Repository: encode/django-rest-framework

Issue:
Translation missing for part of throttle warning
I'm using the throttling mechanism of DRF. When the exception is shown that actually the request was throttled, one part of the message is translated, the other part is not:

![grafik](https://user-images.githubusercontent.com/3176075/45357191-d2864600-b5c5-11e8-95c5-e48e8311fd49.png)

I couldn't find the part "expected available in X seconds" in the translation file so I guess it is just missing?

I'm using `djangorestframework==3.8.2`.

Best regards
Ron 


Discussion:
It looks like the `Throttled` exception doesn't have translations for the extra detail.

https://github.com/encode/django-rest-framework/blob/5f1f2b100353970ac0dec672154fedd3df9d0a9f/rest_framework/exceptions.py#L223-L228
Any quick workaround possible?
Re-raise as a custom Exception class that has translations?
Bump. Just faced this, is it possible to get this fixed in the next minor release?

Rather than a missing translation, this is also looks like a misuse of the translation framework.

Monkey patch the exception in the meantime.
```
# Adding this here so we force the translation to happen
# because DRF is bugged, monkey patch it.
# Remove once https://github.com/encode/django-rest-framework/issues/6177 is fixed
from django.utils.translation import gettext_lazy as _
from rest_framework.exceptions import Throttled
Throttled.extra_detail_singular = _("Expected available in {wait} second.")
Throttled.extra_detail_plural = _("Expected available in {wait} seconds.")
```
Adding this to the milestone, as I don't think there is any significant complexity in fixing this. 

> Rather than a missing translation, this is also looks like a misuse of the translation framework.

Can you clarify? `ngettext` is still used to switch between the two messages.
> 
> 
> Adding this to the milestone, as I don't think there is any significant complexity in fixing this.
> 
> > Rather than a missing translation, this is also looks like a misuse of the translation framework.
> 
> Can you clarify? `ngettext` is still used to switch between the two messages.

I'm not 100% sure about what's the issue, but ngettext doesn't seem to be working unless the text itself is also a translated string. Please see my monkey patch above.

Looks like this line:
```
force_text(ungettext(self.extra_detail_singular.format(wait=wait),
                                     self.extra_detail_plural.format(wait=wait),
                                     wait))))
```
Will only work if extra_detail_singular and extra_detail_plural are lazy translation strings (or translated in place).
Perhaps it is my Windows environment and gettext, but ungettext wasn't giving me translated text even if I added the translation string by hand.

Edit: Probably related to the fact that a variable is used there, and that .format is also used. Please read the django docs for a good example about how to use ungettext: https://docs.djangoproject.com/en/2.2/topics/i18n/translation/#pluralization
> Probably related to the fact that a variable is used there, and that .format is also used. 

This would make sense.

> Please read the django docs for a good example about how to use ungettext

DRF uses `.format` instead of `%`-based formatting, so we can't just pass the strings directly to the various gettext functions. That said, wrapping the format strings in `gettext_lazy` should fix the issue. 

Resolving patch:
diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py
--- a/rest_framework/exceptions.py
+++ b/rest_framework/exceptions.py
@@ -219,8 +219,8 @@ def __init__(self, media_type, detail=None, code=None):
 class Throttled(APIException):
     status_code = status.HTTP_429_TOO_MANY_REQUESTS
     default_detail = _('Request was throttled.')
-    extra_detail_singular = 'Expected available in {wait} second.'
-    extra_detail_plural = 'Expected available in {wait} seconds.'
+    extra_detail_singular = _('Expected available in {wait} second.')
+    extra_detail_plural = _('Expected available in {wait} seconds.')
     default_code = 'throttled'
 
     def __init__(self, wait=None, detail=None, code=None):


Test patch: