Repository: django-helpdesk/django-helpdesk

Issue:
Isolation of translatable strings
Working on templates, I noticed `blocktrans` is frequently used on whole blocks of text and markup. In a few cases, parts of the same text block are also used somewhere else, and this forces translators to process the same text twice.

E.g.

```
{% blocktrans %}
<h2>Submit a Ticket</h2>
<p>Unless otherwise stated, all fields are required. Please provide as descriptive a title and description as possible.</p>
{% endblocktrans %}
```

could be rewritten as

```
<h2>{% trans "Submit a Ticket" %}</h2>
<p>{% trans "Unless otherwise stated, all fields are required." %} {% trans "Please provide as descriptive a title and description as possible." %}</p>
```

All fragments in this example are used in at least one other template or module.

I have already begun working on this, by breaking up blocks of text in templates. In order to preserve all the translation work already done, I also had to update the .po files accordingly (for all translated languages). This also means that eventually all source files will need to be re-uploaded to transifex (not only the main English file).

Before going too far, I would like to know if this is something you would be willing to use. IMO, this would help translators by reducing redundancy, but I admit the process seems a bit unorthodox.

Thanks.
Stefano



Discussion:
Hi Stefano, I think it's a good idea to do it via smaller `{% trans %}` tags. There is no particular way for it to be the way that it currently is except perhaps for laziness; a quick look through the source is showing that there aren't _too_ many other areas like this so hopefully it's a quick change that doesn't require too much re-translation.


Resolving patch:
diff --git a/helpdesk/models.py b/helpdesk/models.py
--- a/helpdesk/models.py
+++ b/helpdesk/models.py
@@ -187,6 +187,8 @@ def __unicode__(self):
 
     class Meta:
         ordering = ('title',)
+        verbose_name = _('Queue')
+        verbose_name_plural = _('Queues')
 
     def _from_address(self):
         """
@@ -244,7 +246,7 @@ class Ticket(models.Model):
         (REOPENED_STATUS, _('Reopened')),
         (RESOLVED_STATUS, _('Resolved')),
         (CLOSED_STATUS, _('Closed')),
-        (DUPLICATE_STATUS, _('Duplicate')),        
+        (DUPLICATE_STATUS, _('Duplicate')),
     )
 
     PRIORITY_CHOICES = (
@@ -443,6 +445,8 @@ def _can_be_resolved(self):
     class Meta:
         get_latest_by = "created"
         ordering = ('id',)
+        verbose_name = _('Ticket')
+        verbose_name_plural = _('Tickets')
 
     def __unicode__(self):
         return u'%s %s' % (self.id, self.title)
@@ -491,7 +495,7 @@ class FollowUp(models.Model):
         )
 
     date = models.DateTimeField(
-        _('Date'), 
+        _('Date'),
         default = timezone.now()
         )
 
@@ -535,6 +539,8 @@ class FollowUp(models.Model):
 
     class Meta:
         ordering = ['date']
+        verbose_name = _('Follow-up')
+        verbose_name_plural = _('Follow-ups')
 
     def __unicode__(self):
         return u'%s' % self.title
@@ -590,6 +596,10 @@ def __unicode__(self):
                 }
         return str
 
+    class Meta:
+        verbose_name = _('Ticket change')
+        verbose_name_plural = _('Ticket changes')
+
 
 def attachment_path(instance, filename):
     """
@@ -652,6 +662,8 @@ def __unicode__(self):
 
     class Meta:
         ordering = ['filename',]
+        verbose_name = _('Attachment')
+        verbose_name_plural = _('Attachments')
 
 
 class PreSetReply(models.Model):
@@ -690,6 +702,8 @@ class PreSetReply(models.Model):
 
     class Meta:
         ordering = ['name',]
+        verbose_name = _('Pre-set reply')
+        verbose_name_plural = _('Pre-set replies')
 
     def __unicode__(self):
         return u'%s' % self.name
@@ -728,6 +742,10 @@ class EscalationExclusion(models.Model):
     def __unicode__(self):
         return u'%s' % self.name
 
+    class Meta:
+        verbose_name = _('Escalation exclusion')
+        verbose_name_plural = _('Escalation exclusions')
+
 
 class EmailTemplate(models.Model):
     """
@@ -771,7 +789,7 @@ class EmailTemplate(models.Model):
         help_text=_('The same context is available here as in plain_text, '
             'above.'),
         )
-    
+
     locale = models.CharField(
         _('Locale'),
         max_length=10,
@@ -785,6 +803,8 @@ def __unicode__(self):
 
     class Meta:
         ordering = ['template_name', 'locale']
+        verbose_name = _('e-mail template')
+        verbose_name_plural = _('e-mail templates')
 
 
 class KBCategory(models.Model):
@@ -811,6 +831,8 @@ def __unicode__(self):
 
     class Meta:
         ordering = ['title',]
+        verbose_name = _('Knowledge base category')
+        verbose_name_plural = _('Knowledge base categories')
 
     def get_absolute_url(self):
         return ('helpdesk_kb_category', (), {'slug': self.slug})
@@ -876,6 +898,8 @@ def __unicode__(self):
 
     class Meta:
         ordering = ['title',]
+        verbose_name = _('Knowledge base item')
+        verbose_name_plural = _('Knowledge base items')
 
     def get_absolute_url(self):
         return ('helpdesk_kb_item', (self.id,))
@@ -885,7 +909,7 @@ def get_absolute_url(self):
 class SavedSearch(models.Model):
     """
     Allow a user to save a ticket search, eg their filtering and sorting
-    options, and optionally share it with other users. This lets people 
+    options, and optionally share it with other users. This lets people
     easily create a set of commonly-used filters, such as:
         * My tickets waiting on me
         * My tickets waiting on submitter
@@ -922,10 +946,15 @@ def __unicode__(self):
         else:
             return u'%s' % self.title
 
+    class Meta:
+        verbose_name = _('Saved search')
+        verbose_name_plural = _('Saved searches')
+
+
 class UserSettings(models.Model):
     """
     A bunch of user-specific settings that we want to be able to define, such
-    as notification preferences and other things that should probably be 
+    as notification preferences and other things that should probably be
     configurable.
 
     We should always refer to user.usersettings.settings['setting_name'].
@@ -961,13 +990,13 @@ def __unicode__(self):
         return u'Preferences for %s' % self.user
 
     class Meta:
-        verbose_name = 'User Settings'
-        verbose_name_plural = 'User Settings'
+        verbose_name = _('User Setting')
+        verbose_name_plural = _('User Settings')
 
 
 def create_usersettings(sender, created_models=[], instance=None, created=False, **kwargs):
     """
-    Helper function to create UserSettings instances as 
+    Helper function to create UserSettings instances as
     required, eg when we first create the UserSettings database
     table via 'syncdb' or when we save a new user.
 
@@ -982,7 +1011,7 @@ def create_usersettings(sender, created_models=[], instance=None, created=False,
     elif UserSettings in created_models:
         # We just created the UserSettings model, lets create a UserSettings
         # entry for each existing user. This will only happen once (at install
-        # time, or at upgrade) when the UserSettings model doesn't already 
+        # time, or at upgrade) when the UserSettings model doesn't already
         # exist.
         for u in User.objects.all():
             try:
@@ -996,8 +1025,8 @@ def create_usersettings(sender, created_models=[], instance=None, created=False,
 
 class IgnoreEmail(models.Model):
     """
-    This model lets us easily ignore e-mails from certain senders when 
-    processing IMAP and POP3 mailboxes, eg mails from postmaster or from 
+    This model lets us easily ignore e-mails from certain senders when
+    processing IMAP and POP3 mailboxes, eg mails from postmaster or from
     known trouble-makers.
     """
     queues = models.ManyToManyField(
@@ -1068,13 +1097,18 @@ def test(self, email):
         else:
             return False
 
+    class Meta:
+        verbose_name = _('Ignored e-mail address')
+        verbose_name_plural = _('Ignored e-mail addresses')
+
+
 class TicketCC(models.Model):
     """
-    Often, there are people who wish to follow a ticket who aren't the 
+    Often, there are people who wish to follow a ticket who aren't the
     person who originally submitted it. This model provides a way for those
     people to follow a ticket.
 
-    In this circumstance, a 'person' could be either an e-mail address or 
+    In this circumstance, a 'person' could be either an e-mail address or
     an existing system user.
     """
 
@@ -1191,12 +1225,12 @@ class CustomField(models.Model):
         blank=True,
         null=True,
         )
-        
+
     empty_selection_list = models.BooleanField(
         _('Add empty first choice to List?'),
         default=False,
         help_text=_('Only for List: adds an empty first entry to the choices list, which enforces that the user makes an active choice.'),
-        )        
+        )
 
     list_values = models.TextField(
         _('List Values'),
@@ -1204,7 +1238,7 @@ class CustomField(models.Model):
         blank=True,
         null=True,
         )
-    
+
     ordering = models.IntegerField(
         _('Ordering'),
         help_text=_('Lower numbers are displayed first; higher numbers are listed later'),
@@ -1235,6 +1269,10 @@ def _choices_as_array(self):
     def __unicode__(self):
         return '%s' % (self.name)
 
+    class Meta:
+        verbose_name = _('Custom field')
+        verbose_name_plural = _('Custom fields')
+
 
 class TicketCustomFieldValue(models.Model):
     ticket = models.ForeignKey(
@@ -1255,11 +1293,15 @@ def __unicode__(self):
     class Meta:
         unique_together = ('ticket', 'field'),
 
+    class Meta:
+        verbose_name = _('Ticket custom field value')
+        verbose_name_plural = _('Ticket custom field values')
+
 
 class TicketDependency(models.Model):
     """
     The ticket identified by `ticket` cannot be resolved until the ticket in `depends_on` has been resolved.
-    To help enforce this, a helper function `can_be_resolved` on each Ticket instance checks that 
+    To help enforce this, a helper function `can_be_resolved` on each Ticket instance checks that
     these have all been resolved.
     """
     ticket = models.ForeignKey(
@@ -1279,3 +1321,5 @@ def __unicode__(self):
 
     class Meta:
         unique_together = ('ticket', 'depends_on')
+        verbose_name = _('Ticket dependency')
+        verbose_name_plural = _('Ticket dependencies')


Test patch: