diff --git a/AUTHORS b/AUTHORS index 487d948445..5c6614a951 100644 --- a/AUTHORS +++ b/AUTHORS @@ -507,6 +507,7 @@ answer newbie questions, and generally made Django that much better: Matt Riggott Matt Robenolt Mattia Larentis + Mattias Loverot mattycakes@gmail.com Max Burstein Max Derkachev diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 052a1d5a9a..11b5585f7a 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -43,9 +43,9 @@ from django.utils.encoding import force_text, python_2_unicode_compatible from django.utils.html import escape, format_html from django.utils.http import urlencode, urlquote from django.utils.safestring import mark_safe -from django.utils.text import capfirst, get_text_list +from django.utils.text import capfirst, format_lazy, get_text_list from django.utils.translation import ( - override as translation_override, string_concat, ugettext as _, ungettext, + override as translation_override, ugettext as _, ungettext, ) from django.views.decorators.csrf import csrf_protect from django.views.generic import RedirectView @@ -258,7 +258,7 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)): if isinstance(form_field.widget, SelectMultiple) and not isinstance(form_field.widget, CheckboxSelectMultiple): msg = _('Hold down "Control", or "Command" on a Mac, to select more than one.') help_text = form_field.help_text - form_field.help_text = string_concat(help_text, ' ', msg) if help_text else msg + form_field.help_text = format_lazy('{} {}', help_text, msg) if help_text else msg return form_field def get_view_on_site_url(self, obj=None): diff --git a/django/contrib/postgres/utils.py b/django/contrib/postgres/utils.py index 1563149c7e..a2bbb72f36 100644 --- a/django/contrib/postgres/utils.py +++ b/django/contrib/postgres/utils.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.core.exceptions import ValidationError from django.utils.functional import SimpleLazyObject -from django.utils.translation import string_concat +from django.utils.text import format_lazy def prefix_validation_error(error, prefix, code, params): @@ -15,10 +15,11 @@ def prefix_validation_error(error, prefix, code, params): return ValidationError( # We can't simply concatenate messages since they might require # their associated parameters to be expressed correctly which - # is not something `string_concat` does. For example, proxied + # is not something `format_lazy` does. For example, proxied # ungettext calls require a count parameter and are converted # to an empty string if they are missing it. - message=string_concat( + message=format_lazy( + '{}{}', SimpleLazyObject(lambda: prefix % params), SimpleLazyObject(lambda: error.message % error_params), ), diff --git a/django/db/models/options.py b/django/db/models/options.py index 38f2fcfefc..f53d3bb59f 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -22,8 +22,8 @@ from django.utils.encoding import ( force_text, python_2_unicode_compatible, smart_text, ) from django.utils.functional import cached_property -from django.utils.text import camel_case_to_spaces -from django.utils.translation import override, string_concat +from django.utils.text import camel_case_to_spaces, format_lazy +from django.utils.translation import override NOT_PROVIDED = object() @@ -197,7 +197,7 @@ class Options(object): # verbose_name_plural is a special case because it uses a 's' # by default. if self.verbose_name_plural is None: - self.verbose_name_plural = string_concat(self.verbose_name, 's') + self.verbose_name_plural = format_lazy('{}s', self.verbose_name) # order_with_respect_and ordering are mutually exclusive. self._ordering_clash = bool(self.ordering and self.order_with_respect_to) @@ -206,7 +206,7 @@ class Options(object): if meta_attrs != {}: raise TypeError("'class Meta' got invalid attribute(s): %s" % ','.join(meta_attrs.keys())) else: - self.verbose_name_plural = string_concat(self.verbose_name, 's') + self.verbose_name_plural = format_lazy('{}s', self.verbose_name) del self.meta # If the db_table wasn't provided, use the app_label + model_name. diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py index c42182d96d..9154ceeb7b 100644 --- a/django/utils/translation/__init__.py +++ b/django/utils/translation/__init__.py @@ -4,9 +4,11 @@ Internationalization support. from __future__ import unicode_literals import re +import warnings from django.utils import six from django.utils.decorators import ContextDecorator +from django.utils.deprecation import RemovedInDjango21Warning from django.utils.encoding import force_text from django.utils.functional import lazy @@ -224,6 +226,10 @@ def _string_concat(*strings): Lazy variant of string concatenation, needed for translations that are constructed from multiple parts. """ + warnings.warn( + 'django.utils.translate.string_concat() is deprecated in ' + 'favor of django.utils.text.format_lazy().', + RemovedInDjango21Warning, stacklevel=2) return ''.join(force_text(s) for s in strings) string_concat = lazy(_string_concat, six.text_type) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index e5151ebadc..7aa58e4ee7 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -25,6 +25,8 @@ details on these changes. * ``django.test.runner.setup_databases()`` will be removed. +* ``django.utils.translation.string_concat()`` will be removed. + .. _deprecation-removed-in-2.0: 2.0 diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 07fa53990e..893ecb5792 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -1107,6 +1107,12 @@ For a complete discussion on the usage of the following see the .. function:: string_concat(*strings) + .. deprecated:: 1.11 + + Use :meth:`django.utils.text.format_lazy` instead. + ``string_concat(*strings)`` can be replaced by + ``format_lazy('{}' * len(strings), *strings)``. + Lazy variant of string concatenation, needed for translations that are constructed from multiple parts. diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 7d9af8d132..498b3ee09f 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -477,3 +477,7 @@ Miscellaneous * ``django.test.runner.setup_databases()`` is moved to :func:`django.test.utils.setup_databases`. The old location is deprecated. + +* ``django.utils.translation.string_concat()`` is deprecated in + favor of :func:`django.utils.text.format_lazy`. ``string_concat(*strings)`` + can be replaced by ``format_lazy('{}' * len(strings), *strings)``. diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 11b62ea4ac..c632c41920 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -489,21 +489,22 @@ directly with the ``number`` argument:: raise forms.ValidationError(self.error_message % number) -Joining strings: ``string_concat()`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Formatting strings: ``format_lazy()`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Standard Python string joins (``''.join([...])``) will not work on lists -containing lazy translation objects. Instead, you can use -:func:`django.utils.translation.string_concat()`, which creates a lazy object -that concatenates its contents *and* converts them to strings only when the -result is included in a string. For example:: +Python's :meth:`str.format()` method will not work when either the +``format_string`` or any of the arguments to :meth:`str.format()` +contains lazy translation objects. Instead, you can use +:func:`django.utils.text.format_lazy()`, which creates a lazy object +that runs the ``str.format()`` method only when the result is included +in a string. For example:: - from django.utils.translation import string_concat + from django.utils.text import format_lazy from django.utils.translation import ugettext_lazy ... name = ugettext_lazy('John Lennon') instrument = ugettext_lazy('guitar') - result = string_concat(name, ': ', instrument) + result = format_lazy('{name}: {instrument}', name=name, instrument=instrument) In this case, the lazy translations in ``result`` will only be converted to strings when ``result`` itself is used in a string (usually at template diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py index b6aeae8caa..b0f77cfe06 100644 --- a/tests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -16,10 +16,12 @@ from django.conf import settings from django.conf.urls.i18n import i18n_patterns from django.template import Context, Template, TemplateSyntaxError from django.test import ( - RequestFactory, SimpleTestCase, TestCase, override_settings, + RequestFactory, SimpleTestCase, TestCase, ignore_warnings, + override_settings, ) from django.utils import six, translation from django.utils._os import upath +from django.utils.deprecation import RemovedInDjango21Warning from django.utils.formats import ( date_format, get_format, get_format_modules, iter_format_modules, localize, localize_input, reset_format_cache, sanitize_separators, time_format, @@ -417,6 +419,7 @@ class TranslationTests(SimpleTestCase): ' super results{% endblocktrans %}' ) + @ignore_warnings(category=RemovedInDjango21Warning) def test_string_concat(self): """ six.text_type(string_concat(...)) should not raise a TypeError - #4796