Skip to content

Commit

Permalink
Replace format() placeholders in translatable strings with % formatting
Browse files Browse the repository at this point in the history
Fixes #5539. Transifex and Django's makemessages command have validation to catch invalid placeholder variables within translated strings - for example, where the translator has translated the variable name - but these only recognise old-style `%` formatting, not the `format` method, and so it's better for us to standardise on % formatting.

To reduce the burden on translators having to re-translate these strings, only the ones using named placeholders (`"Edited page {title}"`) rather than numeric ones (`"Edited page {0}"`) have been changed - hopefully the latter give less room for error.

Also fixed some incorrect use of plurals (verbose_name vs verbose_name_plural) in snippet confirmation messages.
  • Loading branch information
gasman authored and lb- committed Oct 11, 2019
1 parent 6a6555e commit 9d7d09b
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 44 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Changelog
* Fix: Prevent broken images in notification emails when static files are hosted on a remote domain (Eduard Luca)
* Fix: Replace styleguide example avatar with default image to avoid issues when custom user model is used (Matt Westcott)
* Fix: `DraftailRichTextArea` is no longer treated as a hidden field by Django's form logic (Sergey Fedoseev)
* Fix: Replace format() placeholders in translatable strings with % formatting (Matt Westcott)


2.6.2 (19.09.2019)
Expand Down
1 change: 1 addition & 0 deletions docs/releases/2.7.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Bug fixes
* Prevent broken images in notification emails when static files are hosted on a remote domain (Eduard Luca)
* Replace styleguide example avatar with default image to avoid issues when custom user model is used (Matt Westcott)
* ``DraftailRichTextArea`` is no longer treated as a hidden field by Django's form logic (Sergey Fedoseev)
* Replace format() placeholders in translatable strings with % formatting (Matt Westcott)


Upgrade considerations
Expand Down
40 changes: 23 additions & 17 deletions wagtail/admin/wagtail_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,17 @@ def page_listing_buttons(page, page_perms, is_parent=False):
yield PageListingButton(
_('Edit'),
reverse('wagtailadmin_pages:edit', args=[page.id]),
attrs={'aria-label': _("Edit '{title}'").format(title=page.get_admin_display_title())},
attrs={'aria-label': _("Edit '%(title)s'") % {'title': page.get_admin_display_title()}},
priority=10
)
if page.has_unpublished_changes:
yield PageListingButton(
_('View draft'),
reverse('wagtailadmin_pages:view_draft', args=[page.id]),
attrs={'aria-label': _("Preview draft version of '{title}'").format(title=page.get_admin_display_title()), 'target': '_blank', 'rel': 'noopener noreferrer'},
attrs={
'aria-label': _("Preview draft version of '%(title)s'") % {'title': page.get_admin_display_title()},
'target': '_blank', 'rel': 'noopener noreferrer'
},
priority=20
)
if page.live and page.url:
Expand All @@ -119,7 +122,7 @@ def page_listing_buttons(page, page_perms, is_parent=False):
page.url,
attrs={
'target': "_blank", 'rel': 'noopener noreferrer',
'aria-label': _("View live version of '{title}'").format(title=page.get_admin_display_title()),
'aria-label': _("View live version of '%(title)s'") % {'title': page.get_admin_display_title()},
},
priority=30
)
Expand All @@ -129,7 +132,7 @@ def page_listing_buttons(page, page_perms, is_parent=False):
_('Add child page'),
reverse('wagtailadmin_pages:add_subpage', args=[page.id]),
attrs={
'aria-label': _("Add a child page to '{title}' ").format(title=page.get_admin_display_title()),
'aria-label': _("Add a child page to '%(title)s' ") % {'title': page.get_admin_display_title()},
},
classes={'button', 'button-small', 'bicolor', 'icon', 'white', 'icon-plus'},
priority=40
Expand All @@ -138,7 +141,7 @@ def page_listing_buttons(page, page_perms, is_parent=False):
yield PageListingButton(
_('Add child page'),
reverse('wagtailadmin_pages:add_subpage', args=[page.id]),
attrs={'aria-label': _("Add a child page to '{title}' ").format(title=page.get_admin_display_title())},
attrs={'aria-label': _("Add a child page to '%(title)s' ") % {'title': page.get_admin_display_title()}},
priority=40
)

Expand All @@ -148,7 +151,10 @@ def page_listing_buttons(page, page_perms, is_parent=False):
page=page,
page_perms=page_perms,
is_parent=is_parent,
attrs={'target': '_blank', 'rel': 'noopener noreferrer', 'title': _("View more options for '{title}'").format(title=page.get_admin_display_title())},
attrs={
'target': '_blank', 'rel': 'noopener noreferrer',
'title': _("View more options for '%(title)s'") % {'title': page.get_admin_display_title()}
},
priority=50
)

Expand All @@ -159,35 +165,35 @@ def page_listing_more_buttons(page, page_perms, is_parent=False):
yield Button(
_('Move'),
reverse('wagtailadmin_pages:move', args=[page.id]),
attrs={"title": _("Move page '{title}'").format(title=page.get_admin_display_title())},
attrs={"title": _("Move page '%(title)s'") % {'title': page.get_admin_display_title()}},
priority=10
)
if page_perms.can_copy():
yield Button(
_('Copy'),
reverse('wagtailadmin_pages:copy', args=[page.id]),
attrs={'title': _("Copy page '{title}'").format(title=page.get_admin_display_title())},
attrs={'title': _("Copy page '%(title)s'") % {'title': page.get_admin_display_title()}},
priority=20
)
if page_perms.can_delete():
yield Button(
_('Delete'),
reverse('wagtailadmin_pages:delete', args=[page.id]),
attrs={'title': _("Delete page '{title}'").format(title=page.get_admin_display_title())},
attrs={'title': _("Delete page '%(title)s'") % {'title': page.get_admin_display_title()}},
priority=30
)
if page_perms.can_unpublish():
yield Button(
_('Unpublish'),
reverse('wagtailadmin_pages:unpublish', args=[page.id]),
attrs={'title': _("Unpublish page '{title}'").format(title=page.get_admin_display_title())},
attrs={'title': _("Unpublish page '%(title)s'") % {'title': page.get_admin_display_title()}},
priority=40
)
if page_perms.can_view_revisions():
yield Button(
_('Revisions'),
reverse('wagtailadmin_pages:revisions_index', args=[page.id]),
attrs={'title': _("View revision history for '{title}'").format(title=page.get_admin_display_title())},
attrs={'title': _("View revision history for '%(title)s'") % {'title': page.get_admin_display_title()}},
priority=50
)

Expand Down Expand Up @@ -356,7 +362,7 @@ def register_core_features(features):
'draftail', 'h1', draftail_features.BlockFeature({
'label': 'H1',
'type': 'header-one',
'description': ugettext('Heading {level}').format(level=1),
'description': ugettext('Heading %(level)d') % {'level': 1},
})
)
features.register_converter_rule('contentstate', 'h1', {
Expand All @@ -371,7 +377,7 @@ def register_core_features(features):
'draftail', 'h2', draftail_features.BlockFeature({
'label': 'H2',
'type': 'header-two',
'description': ugettext('Heading {level}').format(level=2),
'description': ugettext('Heading %(level)d') % {'level': 2},
})
)
features.register_converter_rule('contentstate', 'h2', {
Expand All @@ -386,7 +392,7 @@ def register_core_features(features):
'draftail', 'h3', draftail_features.BlockFeature({
'label': 'H3',
'type': 'header-three',
'description': ugettext('Heading {level}').format(level=3),
'description': ugettext('Heading %(level)d') % {'level': 3},
})
)
features.register_converter_rule('contentstate', 'h3', {
Expand All @@ -401,7 +407,7 @@ def register_core_features(features):
'draftail', 'h4', draftail_features.BlockFeature({
'label': 'H4',
'type': 'header-four',
'description': ugettext('Heading {level}').format(level=4),
'description': ugettext('Heading %(level)d') % {'level': 4},
})
)
features.register_converter_rule('contentstate', 'h4', {
Expand All @@ -416,7 +422,7 @@ def register_core_features(features):
'draftail', 'h5', draftail_features.BlockFeature({
'label': 'H5',
'type': 'header-five',
'description': ugettext('Heading {level}').format(level=5),
'description': ugettext('Heading %(level)d') % {'level': 5},
})
)
features.register_converter_rule('contentstate', 'h5', {
Expand All @@ -431,7 +437,7 @@ def register_core_features(features):
'draftail', 'h6', draftail_features.BlockFeature({
'label': 'H6',
'type': 'header-six',
'description': ugettext('Heading {level}').format(level=6),
'description': ugettext('Heading %(level)d') % {'level': 6},
})
)
features.register_converter_rule('contentstate', 'h6', {
Expand Down
15 changes: 9 additions & 6 deletions wagtail/contrib/modeladmin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,9 @@ def get_context_data(self, **kwargs):
return super().get_context_data(**context)

def get_success_message(self, instance):
return _("{model_name} '{instance}' created.").format(
model_name=capfirst(self.opts.verbose_name), instance=instance)
return _("%(model_name)s '%(instance)s' created.") % {
'model_name': capfirst(self.opts.verbose_name), 'instance': instance
}

def get_success_message_buttons(self, instance):
button_url = self.url_helper.get_action_url('edit', quote(instance.pk))
Expand Down Expand Up @@ -640,8 +641,9 @@ def get_meta_title(self):
return _('Editing %s') % self.verbose_name

def get_success_message(self, instance):
return _("{model_name} '{instance}' updated.").format(
model_name=capfirst(self.verbose_name), instance=instance)
return _("%(model_name)s '%(instance)s' updated.") % {
'model_name': capfirst(self.verbose_name), 'instance': instance
}

def get_context_data(self, **kwargs):
context = {
Expand Down Expand Up @@ -726,8 +728,9 @@ def delete_instance(self):

def post(self, request, *args, **kwargs):
try:
msg = _("{model} '{instance}' deleted.").format(
model=self.verbose_name, instance=self.instance)
msg = _("%(model_name)s '%(instance)s' deleted.") % {
'model_name': self.verbose_name, 'instance': self.instance
}
self.delete_instance()
messages.success(request, msg)
return redirect(self.index_url)
Expand Down
8 changes: 4 additions & 4 deletions wagtail/contrib/settings/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ def edit(request, app_name, model_name, site_pk):

messages.success(
request,
_("{setting_type} updated.").format(
setting_type=capfirst(setting_type_name),
instance=instance
)
_("%(setting_type)s updated.") % {
'setting_type': capfirst(setting_type_name),
'instance': instance
}
)
return redirect('wagtailsettings:edit', app_name, model_name, site.pk)
else:
Expand Down
2 changes: 1 addition & 1 deletion wagtail/core/blocks/static_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class StaticBlock(Block):
def render_form(self, value, prefix='', errors=None):
if self.meta.admin_text is None:
if self.label:
return _('{label}: this block has no options.').format(label=self.label)
return _('%(label)s: this block has no options.') % {'label': self.label}
else:
return _('This block has no options.')
return self.meta.admin_text
Expand Down
40 changes: 24 additions & 16 deletions wagtail/snippets/views/snippets.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from django.urls import reverse
from django.utils.text import capfirst
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext

from wagtail.admin import messages
from wagtail.admin.auth import permission_denied
Expand Down Expand Up @@ -144,10 +145,10 @@ def create(request, app_label, model_name):

messages.success(
request,
_("{snippet_type} '{instance}' created.").format(
snippet_type=capfirst(model._meta.verbose_name),
instance=instance
),
_("%(snippet_type)s '%(instance)s' created.") % {
'snippet_type': capfirst(model._meta.verbose_name),
'instance': instance
},
buttons=[
messages.button(reverse(
'wagtailsnippets:edit', args=(app_label, model_name, quote(instance.pk))
Expand Down Expand Up @@ -191,10 +192,10 @@ def edit(request, app_label, model_name, pk):

messages.success(
request,
_("{snippet_type} '{instance}' updated.").format(
snippet_type=capfirst(model._meta.verbose_name_plural),
instance=instance
),
_("%(snippet_type)s '%(instance)s' updated.") % {
'snippet_type': capfirst(model._meta.verbose_name),
'instance': instance
},
buttons=[
messages.button(reverse(
'wagtailsnippets:edit', args=(app_label, model_name, quote(instance.pk))
Expand Down Expand Up @@ -239,15 +240,22 @@ def delete(request, app_label, model_name, pk=None):
instance.delete()

if count == 1:
message_content = _("{snippet_type} '{instance}' deleted.").format(
snippet_type=capfirst(model._meta.verbose_name_plural),
instance=instance
)
message_content = _("%(snippet_type)s '%(instance)s' deleted.") % {
'snippet_type': capfirst(model._meta.verbose_name),
'instance': instance
}
else:
message_content = _("{count} {snippet_type} deleted.").format(
snippet_type=capfirst(model._meta.verbose_name_plural),
count=count
)
# This message is only used in plural form, but we'll define it with ungettext so that
# languages with multiple plural forms can be handled correctly (or, at least, as
# correctly as possible within the limitations of verbose_name_plural...)
message_content = ungettext(
"%(count)d %(snippet_type)s deleted.",
"%(count)d %(snippet_type)s deleted.",
count
) % {
'snippet_type': capfirst(model._meta.verbose_name_plural),
'count': count
}

messages.success(request, message_content)

Expand Down

0 comments on commit 9d7d09b

Please sign in to comment.