Index: django/utils/text.py =================================================================== --- django/utils/text.py (revision 6670) +++ django/utils/text.py (working copy) @@ -36,16 +36,22 @@ return u''.join(_generator()) wrap = allow_lazy(wrap, unicode) +word_re = re.compile(r'\S+', re.UNICODE) def truncate_words(s, num): "Truncates a string after a certain number of words." s = force_unicode(s) length = int(num) - words = s.split() - if len(words) > length: - words = words[:length] - if not words[-1].endswith('...'): - words.append('...') - return u' '.join(words) + if length < 1: + return u'' + for count, match in enumerate(word_re.finditer(s)): + if count + 1 == length: # count is 0-based + end = match.end() + if end < len(s): + s = s[:end] + if not s.endswith('...'): + s += ' ...' + break + return s truncate_words = allow_lazy(truncate_words, unicode) def truncate_html_words(s, num): Index: tests/regressiontests/defaultfilters/tests.py =================================================================== --- tests/regressiontests/defaultfilters/tests.py (revision 6670) +++ tests/regressiontests/defaultfilters/tests.py (working copy) @@ -98,6 +98,15 @@ >>> truncatewords(u'A sentence with a few words in it', 'not a number') u'A sentence with a few words in it' +>>> truncatewords(u'Double-spaced sentence with a few words', 2) +u'Double-spaced sentence ...' + +>>> truncatewords(u' Two leading spaces for this sentence', 3) +u' Two leading spaces ...' + +>>> truncatewords(u'Some text\non two lines', 4) +u'Some text\non two ...' + >>> truncatewords_html(u'
one two - three
four five