Repository: zestedesavoir/zds-site

Issue:
Espaces insécables dans le titre des contenus à la Une
Il serait bien d'ajouter un filtre permettant de remplacer les espaces par des espaces insécable là où il faut. _Exemple : avant un point d'interrogation (cf. capture d'écran)._

<img width="228" alt="capture d ecran 2019-02-28 a 20 24 39" src="https://user-images.githubusercontent.com/873762/53592687-1af6e780-3b97-11e9-951b-054ae0d9f67c.png">



Discussion:
Il faudrait utiliser un [templatetag](https://github.com/zestedesavoir/zds-site/tree/dev/zds/utils/templatetags) pour qu'il soit directement utilisable sur le front sans difficulté, ça permettrait de l'ajouter partout où c'est necessaire

https://docs.djangoproject.com/fr/2.1/howto/custom-template-tags/
Je dois ajouter un fichier dans [templatetag](https://github.com/zestedesavoir/zds-site/tree/dev/zds/utils/templatetags) qui gérerait les espaces devant certains caractères ?
Oui comme un filtre : https://github.com/zestedesavoir/zds-site/blob/48004a01acb38ca51b21c5a5a70db6e1cc370fc0/zds/utils/templatetags/date.py
~~je peux m'en occuper de celle là @Helmasaur si tu la sens pas (je m'étais assigné mais déassigné par manque de temps)~~ Nope, je serais pas là la semaine qui vient
J'ai essayé de m'assigner mais il me semble que je ne puisse pas. J'essaie d'apprendre donc je verrais ce que ça donne 😄 .
Le même problème existe pour les titres des sujets, et tutoriels. J'imagine que ça s'étend au billets et aux articles.
Concentre toi sur la Une comme je t'ai dis sur Discord, la majorité des titres sont en no-wrap
J'ai fait les tests pour ces cas là en fait. Donc autant faire les quatre modification ? Je pense que c'est les endroits principaux.
Fait une correction là ou il y en a besoin par contre attention de ne pas impacter la génération du slug dans l'url si tu modifies à l'envoie

Resolving patch:
diff --git a/zds/utils/templatetags/french_typography.py b/zds/utils/templatetags/french_typography.py
new file mode 100644
--- /dev/null
+++ b/zds/utils/templatetags/french_typography.py
@@ -0,0 +1,27 @@
+from django import template
+from django.template.defaultfilters import stringfilter
+from django.utils.safestring import mark_safe
+
+register = template.Library()
+
+
+@register.filter()
+@stringfilter
+def french_typography(str):
+    """
+    Replaces spaces with non-breaking-spaces or narrow non-breaking-spaces
+    before or after some symbols, according to French typography.
+
+    This filter is naive and should not be used on Markdown content.
+    """
+    return mark_safe(
+        # Narrow non-breaking space: &#8239;
+        str.replace(' ;', '&#8239;;')
+           .replace(' ?', '&#8239;?')
+           .replace(' !', '&#8239;!')
+           .replace(' %', '&#8239;%')
+        # Non-breaking space: &nbsp;
+           .replace('« ', '«&nbsp;')
+           .replace(' »', '&nbsp;»')
+           .replace(' :', '&nbsp;:')
+    )


Test patch:
diff --git a/zds/utils/tests/tests_french_typography.py b/zds/utils/tests/tests_french_typography.py
new file mode 100644
--- /dev/null
+++ b/zds/utils/tests/tests_french_typography.py
@@ -0,0 +1,50 @@
+from django.test import TestCase
+from zds.utils.templatetags.french_typography import french_typography
+from django.utils.safestring import mark_safe
+
+
+class FrenchTypographyTests(TestCase):
+
+    @staticmethod
+    def get_cases():
+        return {
+            # Narrow non-breaking space: &#8239;
+            'semicolon':
+                {'input': ' ;',
+                 'expected_output': mark_safe('&#8239;;')},
+            'question mark':
+                {'input': ' ?',
+                 'expected_output': mark_safe('&#8239;?')},
+            'exclamation mark':
+                {'input': ' !',
+                 'expected_output': mark_safe('&#8239;!')},
+            'percent sign':
+                {'input': ' %',
+                 'expected_output': mark_safe('&#8239;%')},
+            # Non-breaking space: &nbsp;
+            'opening double guillemet':
+                {'input': '« ',
+                 'expected_output': mark_safe('«&nbsp;')},
+            'closing double guillemet':
+                {'input': ' »',
+                 'expected_output': mark_safe('&nbsp;»')},
+            'colon':
+                {'input': ' :',
+                 'expected_output': mark_safe('&nbsp;:')},
+            # Miscellaneous
+            'several replacements':
+                {'input': 'Ô râge, oh ?! des zestes poires !',
+                 'expected_output': mark_safe('Ô râge, oh&#8239;?! des zestes poires&#8239;!')},
+            'no replacement':
+                {'input': 'Pulpe Fiction.',
+                 'expected_output': 'Pulpe Fiction.'},
+            'empty string':
+                {'input': '',
+                 'expected_output': ''}
+        }
+
+    def test_french_typography(self):
+        test_cases = FrenchTypographyTests.get_cases()
+        for case_name, case in test_cases.items():
+            with self.subTest(msg=case_name):
+                self.assertEqual(case['expected_output'], french_typography(case['input']))