1
0
mirror of https://github.com/django/django.git synced 2025-10-24 22:26:08 +00:00

Merged i18n branch into the trunk! Fixes #65, and perhaps some others. NB: this means that the i18n branch is now obsolete and will be made read-only.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@1068 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jacob Kaplan-Moss
2005-11-04 04:59:46 +00:00
parent cb45fd0ae2
commit 5cf8f68423
60 changed files with 12449 additions and 186 deletions

View File

@@ -33,6 +33,12 @@ class DjangoContext(Context):
self['user'] = request.user
self['messages'] = request.user.get_and_delete_messages()
self['perms'] = PermWrapper(request.user)
from django.conf import settings
self['LANGUAGES'] = settings.LANGUAGES
if hasattr(request, 'LANGUAGE_CODE'):
self['LANGUAGE_CODE'] = request.LANGUAGE_CODE
else:
self['LANGUAGE_CODE'] = settings.LANGUAGE_CODE
if DEBUG and request.META.get('REMOTE_ADDR') in INTERNAL_IPS:
self['debug'] = True
from django.core import db

View File

@@ -1,8 +1,9 @@
from django.conf import settings
from django.core import formfields, validators
from django.core.exceptions import ObjectDoesNotExist
from django.utils.functional import curry
from django.utils.functional import curry, lazy
from django.utils.text import capfirst
from django.utils.translation import gettext_lazy
import datetime, os
# Random entropy string used by "default" param.
@@ -26,6 +27,16 @@ prep_for_like_query = lambda x: str(x).replace("%", "\%").replace("_", "\_")
# returns the <ul> class for a given radio_admin value
get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
def string_concat(*strings):
""""
lazy variant of string concatenation, needed for translations that are
constructed from multiple parts. Handles lazy strings and non-strings by
first turning all arguments to strings, before joining them.
"""
return ''.join([str(el) for el in strings])
string_concat = lazy(string_concat, str)
def manipulator_valid_rel_key(f, self, field_data, all_data):
"Validates that the value is a valid foreign key"
mod = f.rel.to.get_model_module()
@@ -80,9 +91,11 @@ class Field(object):
self.db_column = db_column
if rel and isinstance(rel, ManyToMany):
if rel.raw_id_admin:
self.help_text += ' Separate multiple IDs with commas.'
self.help_text = string_concat(self.help_text,
gettext_lazy(' Separate multiple IDs with commas.'))
else:
self.help_text += ' Hold down "Control", or "Command" on a Mac, to select more than one.'
self.help_text = string_concat(self.help_text,
gettext_lazy(' Hold down "Control", or "Command" on a Mac, to select more than one.'))
# Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
if db_index is None:

View File

@@ -234,6 +234,97 @@ class Parser:
def delete_first_token(self):
del self.tokens[0]
class TokenParser:
"""
You need to subclass this class and implement the
top method to parse your template line. When instantiating
the parser, you pass in the line from the django template
parser.
If your tag needs to know what tag name it was called with,
you find it in the tagname instance variable of the parser.
"""
def __init__(self, subject):
self.subject = subject
self.pointer = 0
self.backout = []
self.tagname = self.tag()
def top(self):
"""
You need to overload this method to do the actual parsing
and return the result.
"""
raise NotImplemented
def more(self):
"""
This returns True if there is more stuff in the tag.
"""
return self.pointer < len(self.subject)
def back(self):
"""
This method undos the last microparser. This can be
used for lookahead and backtracking.
"""
if not len(self.backout):
raise TemplateSyntaxError, "back called without some previous parsing"
self.pointer = self.backout.pop()
def tag(self):
"""
This microparser just returns the next tag from the line.
"""
subject = self.subject
i = self.pointer
if i >= len(subject):
raise TemplateSyntaxError, "expected another tag, found end of string: %s" % subject
p = i
while i < len(subject) and subject[i] not in (' ', '\t'):
i += 1
s = subject[p:i]
while i < len(subject) and subject[i] in (' ', '\t'):
i += 1
self.backout.append(self.pointer)
self.pointer = i
return s
def value(self):
"""
This microparser parses for a value - some string constant or
variable name.
"""
subject = self.subject
i = self.pointer
if i >= len(subject):
raise TemplateSyntaxError, "searching for value expected another value, found end of string: %s" % subject
if subject[i] in ('"', "'"):
p = i
i += 1
while i < len(subject) and subject[i] != subject[p]:
i += 1
if i >= len(subject):
raise TemplateSyntaxError, "searching for value, unexpected end of string in column %d: %s" % subject
i += 1
res = subject[p:i]
while i < len(subject) and subject[i] in (' ', '\t'):
i += 1
self.backout.append(self.pointer)
self.pointer = i
return res
else:
p = i
while i < len(subject) and subject[i] not in (' ', '\t'):
i += 1
s = subject[p:i]
while i < len(subject) and subject[i] in (' ', '\t'):
i += 1
self.backout.append(self.pointer)
self.pointer = i
return s
class FilterParser:
"""Parse a variable token and its optional filters (all as a single string),
and return a list of tuples of the filter name and arguments.
@@ -255,8 +346,12 @@ class FilterParser:
self.filters = []
self.current_filter_name = None
self.current_filter_arg = None
# First read the variable part
self.var = self.read_alphanumeric_token()
# First read the variable part - decide on wether we need
# to parse a string or a variable by peeking into the stream
if self.peek_char() in ('_', '"', "'"):
self.var = self.read_constant_string_token()
else:
self.var = self.read_alphanumeric_token()
if not self.var:
raise TemplateSyntaxError, "Could not read variable name: '%s'" % self.s
if self.var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or self.var[0] == '_':
@@ -269,6 +364,12 @@ class FilterParser:
# We have a filter separator; start reading the filters
self.read_filters()
def peek_char(self):
try:
return self.s[self.i+1]
except IndexError:
return None
def next_char(self):
self.i = self.i + 1
try:
@@ -276,6 +377,37 @@ class FilterParser:
except IndexError:
self.current = None
def read_constant_string_token(self):
"""Read a constant string that must be delimited by either "
or ' characters. The string is returned with it's delimiters."""
val = ''
qchar = None
i18n = False
self.next_char()
if self.current == '_':
i18n = True
self.next_char()
if self.current != '(':
raise TemplateSyntaxError, "Bad character (expecting '(') '%s'" % self.current
self.next_char()
if not self.current in ('"', "'"):
raise TemplateSyntaxError, "Bad character (expecting '\"' or ''') '%s'" % self.current
qchar = self.current
val += qchar
while 1:
self.next_char()
if self.current == qchar:
break
val += self.current
val += self.current
self.next_char()
if i18n:
if self.current != ')':
raise TemplateSyntaxError, "Bad character (expecting ')') '%s'" % self.current
self.next_char()
val = qchar+_(val.strip(qchar))+qchar
return val
def read_alphanumeric_token(self):
"""Read a variable name or filter name, which are continuous strings of
alphanumeric characters + the underscore"""
@@ -318,8 +450,15 @@ class FilterParser:
return (self.current_filter_name, self.current_filter_arg)
def read_arg(self):
# First read a "
# First read a " or a _("
self.next_char()
translated = False
if self.current == '_':
self.next_char()
if self.current != '(':
raise TemplateSyntaxError, "Bad character (expecting '(') '%s'" % self.current
translated = True
self.next_char()
if self.current != '"':
raise TemplateSyntaxError, "Bad character (expecting '\"') '%s'" % self.current
self.escaped = False
@@ -346,6 +485,11 @@ class FilterParser:
arg += self.current
# self.current must now be '"'
self.next_char()
if translated:
if self.current != ')':
raise TemplateSyntaxError, "Bad character (expecting ')') '%s'" % self.current
self.next_char()
arg = _(arg)
return arg
def get_filters_from_token(token):

View File

@@ -2,7 +2,10 @@
from django.core.template import Node, NodeList, Template, Context, resolve_variable, resolve_variable_with_filters, get_filters_from_token, registered_filters
from django.core.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, register_tag
from django.utils import translation
import sys
import re
class CommentNode(Node):
def render(self, context):

View File

@@ -25,6 +25,7 @@ slug_re = re.compile(r'^[-\w]+$')
url_re = re.compile(r'^http://\S+$')
from django.conf.settings import JING_PATH
from django.utils.translation import gettext_lazy, ngettext
class ValidationError(Exception):
def __init__(self, message):
@@ -54,11 +55,11 @@ class CriticalValidationError(Exception):
def isAlphaNumeric(field_data, all_data):
if not alnum_re.search(field_data):
raise ValidationError, "This value must contain only letters, numbers and underscores."
raise ValidationError, _("This value must contain only letters, numbers and underscores.")
def isAlphaNumericURL(field_data, all_data):
if not alnumurl_re.search(field_data):
raise ValidationError, "This value must contain only letters, numbers, underscores or slashes."
raise ValidationError, _("This value must contain only letters, numbers, underscores and slashes.")
def isSlug(field_data, all_data):
if not slug_re.search(field_data):
@@ -66,18 +67,18 @@ def isSlug(field_data, all_data):
def isLowerCase(field_data, all_data):
if field_data.lower() != field_data:
raise ValidationError, "Uppercase letters are not allowed here."
raise ValidationError, _("Uppercase letters are not allowed here.")
def isUpperCase(field_data, all_data):
if field_data.upper() != field_data:
raise ValidationError, "Lowercase letters are not allowed here."
raise ValidationError, _("Lowercase letters are not allowed here.")
def isCommaSeparatedIntegerList(field_data, all_data):
for supposed_int in field_data.split(','):
try:
int(supposed_int)
except ValueError:
raise ValidationError, "Enter only digits separated by commas."
raise ValidationError, _("Enter only digits separated by commas.")
def isCommaSeparatedEmailList(field_data, all_data):
"""
@@ -89,51 +90,51 @@ def isCommaSeparatedEmailList(field_data, all_data):
try:
isValidEmail(supposed_email.strip(), '')
except ValidationError:
raise ValidationError, "Enter valid e-mail addresses separated by commas."
raise ValidationError, _("Enter valid e-mail addresses separated by commas.")
def isValidIPAddress4(field_data, all_data):
if ip4_re.search(field_data):
valid_parts = [el for el in field_data.split('.') if 0 <= int(el) <= 255]
if len(valid_parts) == 4:
return
raise validators.ValidationError, "Please enter a valid IP address."
raise validators.ValidationError, _("Please enter a valid IP address.")
def isNotEmpty(field_data, all_data):
if field_data.strip() == '':
raise ValidationError, "Empty values are not allowed here."
raise ValidationError, _("Empty values are not allowed here.")
def isOnlyDigits(field_data, all_data):
if not field_data.isdigit():
raise ValidationError, "Non-numeric characters aren't allowed here."
raise ValidationError, _("Non-numeric characters aren't allowed here.")
def isNotOnlyDigits(field_data, all_data):
if field_data.isdigit():
raise ValidationError, "This value can't be comprised solely of digits."
raise ValidationError, _("This value can't be comprised solely of digits.")
def isInteger(field_data, all_data):
# This differs from isOnlyDigits because this accepts the negative sign
if not integer_re.search(field_data):
raise ValidationError, "Enter a whole number."
raise ValidationError, _("Enter a whole number.")
def isOnlyLetters(field_data, all_data):
if not field_data.isalpha():
raise ValidationError, "Only alphabetical characters are allowed here."
raise ValidationError, _("Only alphabetical characters are allowed here.")
def isValidANSIDate(field_data, all_data):
if not ansi_date_re.search(field_data):
raise ValidationError, 'Enter a valid date in YYYY-MM-DD format.'
raise ValidationError, _('Enter a valid date in YYYY-MM-DD format.')
def isValidANSITime(field_data, all_data):
if not ansi_time_re.search(field_data):
raise ValidationError, 'Enter a valid time in HH:MM format.'
raise ValidationError, _('Enter a valid time in HH:MM format.')
def isValidANSIDatetime(field_data, all_data):
if not ansi_datetime_re.search(field_data):
raise ValidationError, 'Enter a valid date/time in YYYY-MM-DD HH:MM format.'
raise ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
def isValidEmail(field_data, all_data):
if not email_re.search(field_data):
raise ValidationError, 'Enter a valid e-mail address.'
raise ValidationError, _('Enter a valid e-mail address.')
def isValidImage(field_data, all_data):
"""
@@ -145,18 +146,18 @@ def isValidImage(field_data, all_data):
try:
Image.open(StringIO(field_data['content']))
except IOError: # Python Imaging Library doesn't recognize it as an image
raise ValidationError, "Upload a valid image. The file you uploaded was either not an image or a corrupted image."
raise ValidationError, _("Upload a valid image. The file you uploaded was either not an image or a corrupted image.")
def isValidImageURL(field_data, all_data):
uc = URLMimeTypeCheck(('image/jpeg', 'image/gif', 'image/png'))
try:
uc(field_data, all_data)
except URLMimeTypeCheck.InvalidContentType:
raise ValidationError, "The URL %s does not point to a valid image." % field_data
raise ValidationError, _("The URL %s does not point to a valid image.") % field_data
def isValidPhone(field_data, all_data):
if not phone_re.search(field_data):
raise ValidationError, 'Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.' % field_data
raise ValidationError, _('Phone numbers must be in XXX-XXX-XXXX format. "%s" is invalid.') % field_data
def isValidQuicktimeVideoURL(field_data, all_data):
"Checks that the given URL is a video that can be played by QuickTime (qt, mpeg)"
@@ -164,11 +165,11 @@ def isValidQuicktimeVideoURL(field_data, all_data):
try:
uc(field_data, all_data)
except URLMimeTypeCheck.InvalidContentType:
raise ValidationError, "The URL %s does not point to a valid QuickTime video." % field_data
raise ValidationError, _("The URL %s does not point to a valid QuickTime video.") % field_data
def isValidURL(field_data, all_data):
if not url_re.search(field_data):
raise ValidationError, "A valid URL is required."
raise ValidationError, _("A valid URL is required.")
def isValidHTML(field_data, all_data):
import urllib, urllib2
@@ -182,14 +183,14 @@ def isValidHTML(field_data, all_data):
return
from xml.dom.minidom import parseString
error_messages = [e.firstChild.wholeText for e in parseString(u.read()).getElementsByTagName('messages')[0].getElementsByTagName('msg')]
raise ValidationError, "Valid HTML is required. Specific errors are:\n%s" % "\n".join(error_messages)
raise ValidationError, _("Valid HTML is required. Specific errors are:\n%s") % "\n".join(error_messages)
def isWellFormedXml(field_data, all_data):
from xml.dom.minidom import parseString
try:
parseString(field_data)
except Exception, e: # Naked except because we're not sure what will be thrown
raise ValidationError, "Badly formed XML: %s" % str(e)
raise ValidationError, _("Badly formed XML: %s") % str(e)
def isWellFormedXmlFragment(field_data, all_data):
isWellFormedXml('<root>%s</root>' % field_data, all_data)
@@ -199,15 +200,15 @@ def isExistingURL(field_data, all_data):
try:
u = urllib2.urlopen(field_data)
except ValueError:
raise ValidationError, "Invalid URL: %s" % field_data
raise ValidationError, _("Invalid URL: %s") % field_data
except: # urllib2.HTTPError, urllib2.URLError, httplib.InvalidURL, etc.
raise ValidationError, "The URL %s is a broken link." % field_data
raise ValidationError, _("The URL %s is a broken link.") % field_data
def isValidUSState(field_data, all_data):
"Checks that the given string is a valid two-letter U.S. state abbreviation"
states = ['AA', 'AE', 'AK', 'AL', 'AP', 'AR', 'AS', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'FM', 'GA', 'GU', 'HI', 'IA', 'ID', 'IL', 'IN', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME', 'MH', 'MI', 'MN', 'MO', 'MP', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH', 'OK', 'OR', 'PA', 'PR', 'PW', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VI', 'VT', 'WA', 'WI', 'WV', 'WY']
if field_data.upper() not in states:
raise ValidationError, "Enter a valid U.S. state abbreviation."
raise ValidationError, _("Enter a valid U.S. state abbreviation.")
def hasNoProfanities(field_data, all_data):
"""
@@ -222,15 +223,14 @@ def hasNoProfanities(field_data, all_data):
if words_seen:
from django.utils.text import get_text_list
plural = len(words_seen) > 1
raise ValidationError, "Watch your mouth! The word%s %s %s not allowed here." % \
(plural and 's' or '',
get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in words_seen], 'and'),
plural and 'are' or 'is')
raise ValidationError, ngettext("Watch your mouth! The word %s is not allowed here.",
"Watch your mouth! The words %s are not allowed here.", plural) % \
get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in words_seen], 'and')
class AlwaysMatchesOtherField:
def __init__(self, other_field_name, error_message=None):
self.other = other_field_name
self.error_message = error_message or "This field must match the '%s' field." % self.other
self.error_message = error_message or gettext_lazy("This field must match the '%s' field.") % self.other
self.always_test = True
def __call__(self, field_data, all_data):
@@ -249,7 +249,7 @@ class ValidateIfOtherFieldEquals:
v(field_data, all_data)
class RequiredIfOtherFieldNotGiven:
def __init__(self, other_field_name, error_message="Please enter something for at least one field."):
def __init__(self, other_field_name, error_message=gettext_lazy("Please enter something for at least one field.")):
self.other, self.error_message = other_field_name, error_message
self.always_test = True
@@ -258,7 +258,7 @@ class RequiredIfOtherFieldNotGiven:
raise ValidationError, self.error_message
class RequiredIfOtherFieldsGiven:
def __init__(self, other_field_names, error_message="Please enter both fields or leave them both empty."):
def __init__(self, other_field_names, error_message=gettext_lazy("Please enter both fields or leave them both empty.")):
self.other, self.error_message = other_field_names, error_message
self.always_test = True
@@ -269,14 +269,15 @@ class RequiredIfOtherFieldsGiven:
class RequiredIfOtherFieldGiven(RequiredIfOtherFieldsGiven):
"Like RequiredIfOtherFieldsGiven, but takes a single field name instead of a list."
def __init__(self, other_field_name, error_message="Please enter both fields or leave them both empty."):
def __init__(self, other_field_name, error_message=gettext_lazy("Please enter both fields or leave them both empty.")):
RequiredIfOtherFieldsGiven.__init__(self, [other_field_name], error_message)
class RequiredIfOtherFieldEquals:
def __init__(self, other_field, other_value, error_message=None):
self.other_field = other_field
self.other_value = other_value
self.error_message = error_message or "This field must be given if %s is %s" % (other_field, other_value)
self.error_message = error_message or gettext_lazy("This field must be given if %(field)s is %(value)s") % {
'field': other_field, 'value': other_value}
self.always_test = True
def __call__(self, field_data, all_data):
@@ -287,7 +288,8 @@ class RequiredIfOtherFieldDoesNotEqual:
def __init__(self, other_field, other_value, error_message=None):
self.other_field = other_field
self.other_value = other_value
self.error_message = error_message or "This field must be given if %s is not %s" % (other_field, other_value)
self.error_message = error_message or gettext_lazy("This field must be given if %(field)s is not %(value)s") % {
'field': other_field, 'value': other_value}
self.always_test = True
def __call__(self, field_data, all_data):
@@ -305,7 +307,7 @@ class IsLessThanOtherField:
class UniqueAmongstFieldsWithPrefix:
def __init__(self, field_name, prefix, error_message):
self.field_name, self.prefix = field_name, prefix
self.error_message = error_message or "Duplicate values are not allowed."
self.error_message = error_message or gettext_lazy("Duplicate values are not allowed.")
def __call__(self, field_data, all_data):
for field_name, value in all_data.items():
@@ -328,7 +330,7 @@ class IsAPowerOf:
from math import log
val = log(int(field_data)) / log(self.power_of)
if val != int(val):
raise ValidationError, "This value must be a power of %s." % self.power_of
raise ValidationError, _("This value must be a power of %s.") % self.power_of
class IsValidFloat:
def __init__(self, max_digits, decimal_places):
@@ -339,13 +341,13 @@ class IsValidFloat:
try:
float(data)
except ValueError:
raise ValidationError, "Please enter a valid decimal number."
raise ValidationError, _("Please enter a valid decimal number.")
if len(data) > (self.max_digits + 1):
raise ValidationError, "Please enter a valid decimal number with at most %s total digit%s." % \
(self.max_digits, self.max_digits > 1 and 's' or '')
raise ValidationError, ngettext( "Please enter a valid decimal number with at most %s total digit.",
"Please enter a valid decimal number with at most %s total digits.", self.max_digits) % self.max_digits
if '.' in data and len(data.split('.')[1]) > self.decimal_places:
raise ValidationError, "Please enter a valid decimal number with at most %s decimal place%s." % \
(self.decimal_places, self.decimal_places > 1 and 's' or '')
raise ValidationError, ngettext("Please enter a valid decimal number with at most %s decimal place.",
"Please enter a valid decimal number with at most %s decimal places.", self.decimal_places) % self.decimal_places
class HasAllowableSize:
"""
@@ -354,8 +356,8 @@ class HasAllowableSize:
"""
def __init__(self, min_size=None, max_size=None, min_error_message=None, max_error_message=None):
self.min_size, self.max_size = min_size, max_size
self.min_error_message = min_error_message or "Make sure your uploaded file is at least %s bytes big." % min_size
self.max_error_message = max_error_message or "Make sure your uploaded file is at most %s bytes big." % min_size
self.min_error_message = min_error_message or gettext_lazy("Make sure your uploaded file is at least %s bytes big.") % min_size
self.max_error_message = max_error_message or gettext_lazy("Make sure your uploaded file is at most %s bytes big.") % min_size
def __call__(self, field_data, all_data):
if self.min_size is not None and len(field_data['content']) < self.min_size:
@@ -368,7 +370,7 @@ class MatchesRegularExpression:
Checks that the field matches the given regular-expression. The regex
should be in string format, not already compiled.
"""
def __init__(self, regexp, error_message="The format for this field is wrong."):
def __init__(self, regexp, error_message=gettext_lazy("The format for this field is wrong.")):
self.regexp = re.compile(regexp)
self.error_message = error_message
@@ -383,7 +385,7 @@ class AnyValidator:
as a validation error. The message is rather unspecific, so it's best to
specify one on instantiation.
"""
def __init__(self, validator_list=[], error_message="This field is invalid."):
def __init__(self, validator_list=[], error_message=gettext_lazy("This field is invalid.")):
self.validator_list = validator_list
self.error_message = error_message
for v in validator_list:
@@ -418,10 +420,11 @@ class URLMimeTypeCheck:
try:
info = urllib2.urlopen(field_data).info()
except (urllib2.HTTPError, urllib2.URLError):
raise URLMimeTypeCheck.CouldNotRetrieve, "Could not retrieve anything from %s." % field_data
raise URLMimeTypeCheck.CouldNotRetrieve, _("Could not retrieve anything from %s.") % field_data
content_type = info['content-type']
if content_type not in self.mime_type_list:
raise URLMimeTypeCheck.InvalidContentType, "The URL %s returned the invalid Content-Type header '%s'." % (field_data, content_type)
raise URLMimeTypeCheck.InvalidContentType, _("The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'.") % {
'url': field_data, 'contenttype': content_type}
class RelaxNGCompact:
"Validate against a Relax NG compact schema"
@@ -453,31 +456,31 @@ class RelaxNGCompact:
# Scrape the Jing error messages to reword them more nicely.
m = re.search(r'Expected "(.*?)" to terminate element starting on line (\d+)', message)
if m:
display_errors.append('Please close the unclosed %s tag from line %s. (Line starts with "%s".)' % \
(m.group(1).replace('/', ''), m.group(2), lines[int(m.group(2)) - 1][:30]))
display_errors.append(_('Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "%(start)s".)') % \
{'tag':m.group(1).replace('/', ''), 'line':m.group(2), 'start':lines[int(m.group(2)) - 1][:30]})
continue
if message.strip() == 'text not allowed here':
display_errors.append('Some text starting on line %s is not allowed in that context. (Line starts with "%s".)' % \
(line, lines[int(line) - 1][:30]))
display_errors.append(_('Some text starting on line %(line)s is not allowed in that context. (Line starts with "%(start)s".)') % \
{'line':line, 'start':lines[int(line) - 1][:30]})
continue
m = re.search(r'\s*attribute "(.*?)" not allowed at this point; ignored', message)
if m:
display_errors.append('"%s" on line %s is an invalid attribute. (Line starts with "%s".)' % \
(m.group(1), line, lines[int(line) - 1][:30]))
display_errors.append(_('"%(attr)s" on line %(line)s is an invalid attribute. (Line starts with "%(start)s".)') % \
{'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
continue
m = re.search(r'\s*unknown element "(.*?)"', message)
if m:
display_errors.append('"<%s>" on line %s is an invalid tag. (Line starts with "%s".)' % \
(m.group(1), line, lines[int(line) - 1][:30]))
display_errors.append(_('"<%(tag)s>" on line %(line)s is an invalid tag. (Line starts with "%(start)s".)') % \
{'tag':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
continue
if message.strip() == 'required attributes missing':
display_errors.append('A tag on line %s is missing one or more required attributes. (Line starts with "%s".)' % \
(line, lines[int(line) - 1][:30]))
display_errors.append(_('A tag on line %(line)s is missing one or more required attributes. (Line starts with "%(start)s".)') % \
{'line':line, 'start':lines[int(line) - 1][:30]})
continue
m = re.search(r'\s*bad value for attribute "(.*?)"', message)
if m:
display_errors.append('The "%s" attribute on line %s has an invalid value. (Line starts with "%s".)' % \
(m.group(1), line, lines[int(line) - 1][:30]))
display_errors.append(_('The "%(attr)s" attribute on line %(line)s has an invalid value. (Line starts with "%(start)s".)') % \
{'attr':m.group(1), 'line':line, 'start':lines[int(line) - 1][:30]})
continue
# Failing all those checks, use the default error message.
display_error = 'Line %s: %s [%s]' % (line, message, level.strip())