mirror of
https://github.com/Hopiu/django-rosetta.git
synced 2026-03-16 21:30:24 +00:00
Fixed unicode handling in gettext headers. Fixes #259
This commit is contained in:
parent
417094f0f1
commit
cc12a30761
6 changed files with 52 additions and 49 deletions
1
CHANGES
1
CHANGES
|
|
@ -6,6 +6,7 @@ Version 0.9.7 (unreleased)
|
||||||
--------------------------
|
--------------------------
|
||||||
* Arabic translation. (#257, thanks @Bashar)
|
* Arabic translation. (#257, thanks @Bashar)
|
||||||
* Translations via the DeepL API (#258, thanks @halitcelik)
|
* Translations via the DeepL API (#258, thanks @halitcelik)
|
||||||
|
* Fixed unicode handling in gettext headers (#259, thanks @NotSqrt)
|
||||||
|
|
||||||
|
|
||||||
Version 0.9.6
|
Version 0.9.6
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
from django import template
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
from django.utils.html import escape
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
from django import template
|
||||||
|
from django.utils.html import escape
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from rosetta.access import can_translate
|
from rosetta.access import can_translate
|
||||||
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
rx = re.compile(r'(%(\([^\s\)]*\))?[sd]|\{[\w\d_]+?\})')
|
rx = re.compile(r'(%(\([^\s\)]*\))?[sd]|\{[\w\d_]+?\})')
|
||||||
|
|
||||||
|
|
@ -14,33 +14,45 @@ can_translate = register.filter(can_translate)
|
||||||
|
|
||||||
|
|
||||||
def format_message(message):
|
def format_message(message):
|
||||||
return mark_safe(rx.sub('<code>\\1</code>', escape(message).replace(r'\n', '<br />\n')))
|
return mark_safe(
|
||||||
|
rx.sub('<code>\\1</code>', escape(message).replace(r'\n', '<br />\n'))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
format_message = register.filter(format_message)
|
format_message = register.filter(format_message)
|
||||||
|
|
||||||
|
|
||||||
def lines_count(message):
|
def lines_count(message):
|
||||||
return 1 + sum([len(line) / 50 for line in message.split('\n')])
|
return 1 + sum([len(line) / 50 for line in message.split('\n')])
|
||||||
|
|
||||||
|
|
||||||
lines_count = register.filter(lines_count)
|
lines_count = register.filter(lines_count)
|
||||||
|
|
||||||
|
|
||||||
def mult(a, b):
|
def mult(a, b):
|
||||||
return int(a) * int(b)
|
return int(a) * int(b)
|
||||||
|
|
||||||
|
|
||||||
mult = register.filter(mult)
|
mult = register.filter(mult)
|
||||||
|
|
||||||
|
|
||||||
def minus(a, b):
|
def minus(a, b):
|
||||||
try:
|
try:
|
||||||
return int(a) - int(b)
|
return int(a) - int(b)
|
||||||
except:
|
except Exception:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
minus = register.filter(minus)
|
minus = register.filter(minus)
|
||||||
|
|
||||||
|
|
||||||
def gt(a, b):
|
def gt(a, b):
|
||||||
try:
|
try:
|
||||||
return int(a) > int(b)
|
return int(a) > int(b)
|
||||||
except:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
gt = register.filter(gt)
|
gt = register.filter(gt)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -54,6 +66,8 @@ def do_incr(parser, token):
|
||||||
if name not in parser._namedIncrNodes:
|
if name not in parser._namedIncrNodes:
|
||||||
parser._namedIncrNodes[name] = IncrNode(0)
|
parser._namedIncrNodes[name] = IncrNode(0)
|
||||||
return parser._namedIncrNodes[name]
|
return parser._namedIncrNodes[name]
|
||||||
|
|
||||||
|
|
||||||
do_incr = register.tag('increment', do_incr)
|
do_incr = register.tag('increment', do_incr)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -68,4 +82,6 @@ class IncrNode(template.Node):
|
||||||
|
|
||||||
def is_fuzzy(message):
|
def is_fuzzy(message):
|
||||||
return message and hasattr(message, 'flags') and 'fuzzy' in message.flags
|
return message and hasattr(message, 'flags') and 'fuzzy' in message.flags
|
||||||
|
|
||||||
|
|
||||||
is_fuzzy = register.filter(is_fuzzy)
|
is_fuzzy = register.filter(is_fuzzy)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
import vcr
|
||||||
from django import VERSION
|
from django import VERSION
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
@ -13,9 +14,6 @@ from django.test import RequestFactory, TestCase, override_settings
|
||||||
from django.test.client import Client
|
from django.test.client import Client
|
||||||
from django.urls import resolve, reverse
|
from django.urls import resolve, reverse
|
||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
|
|
||||||
import six
|
|
||||||
import vcr
|
|
||||||
from rosetta import views
|
from rosetta import views
|
||||||
from rosetta.signals import entry_changed, post_save
|
from rosetta.signals import entry_changed, post_save
|
||||||
from rosetta.storage import get_storage
|
from rosetta.storage import get_storage
|
||||||
|
|
@ -138,10 +136,10 @@ class RosettaTestCase(TestCase):
|
||||||
self.copy_po_file_from_template('./django.po.issue67.template')
|
self.copy_po_file_from_template('./django.po.issue67.template')
|
||||||
|
|
||||||
# Make sure the plurals string is valid
|
# Make sure the plurals string is valid
|
||||||
with open(self.dest_file, 'rb') as f_:
|
with open(self.dest_file, 'r') as f_:
|
||||||
content = f_.read()
|
content = f_.read()
|
||||||
self.assertTrue('Hello, world' not in six.text_type(content))
|
self.assertTrue('Hello, world' not in content)
|
||||||
self.assertTrue('|| n%100>=20) ? 1 : 2)' in six.text_type(content))
|
self.assertTrue('|| n%100>=20) ? 1 : 2)' in content)
|
||||||
del content
|
del content
|
||||||
|
|
||||||
r = self.client.get(self.xx_form_url + '?msg_filter=untranslated')
|
r = self.client.get(self.xx_form_url + '?msg_filter=untranslated')
|
||||||
|
|
@ -157,7 +155,7 @@ class RosettaTestCase(TestCase):
|
||||||
self.client.post(self.xx_form_url + '?msg_filter=untranslated', data)
|
self.client.post(self.xx_form_url + '?msg_filter=untranslated', data)
|
||||||
|
|
||||||
# Make sure the plurals string is still valid
|
# Make sure the plurals string is still valid
|
||||||
with open(self.dest_file, 'rb') as f_:
|
with open(self.dest_file, 'r') as f_:
|
||||||
content = f_.read()
|
content = f_.read()
|
||||||
self.assertTrue('Hello, world' in str(content))
|
self.assertTrue('Hello, world' in str(content))
|
||||||
self.assertTrue('|| n%100>=20) ? 1 : 2)' in str(content))
|
self.assertTrue('|| n%100>=20) ? 1 : 2)' in str(content))
|
||||||
|
|
@ -277,13 +275,13 @@ class RosettaTestCase(TestCase):
|
||||||
# this user.
|
# this user.
|
||||||
with self.settings(ROSETTA_REQUIRES_AUTH=True):
|
with self.settings(ROSETTA_REQUIRES_AUTH=True):
|
||||||
r = self.client3.get(self.xx_form_url)
|
r = self.client3.get(self.xx_form_url)
|
||||||
self.assertFalse(r.content)
|
self.assertFalse(r.content.decode())
|
||||||
self.assertEqual(r.status_code, 302)
|
self.assertEqual(r.status_code, 302)
|
||||||
|
|
||||||
# When it's not required, we sail through.
|
# When it's not required, we sail through.
|
||||||
with self.settings(ROSETTA_REQUIRES_AUTH=False):
|
with self.settings(ROSETTA_REQUIRES_AUTH=False):
|
||||||
r = self.client3.get(self.xx_form_url)
|
r = self.client3.get(self.xx_form_url)
|
||||||
self.assertTrue(r.content)
|
self.assertTrue(r.content.decode())
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
@override_settings(ROSETTA_LANGUAGES=(('fr', 'French'), ('xx', 'Dummy Language')))
|
@override_settings(ROSETTA_LANGUAGES=(('fr', 'French'), ('xx', 'Dummy Language')))
|
||||||
|
|
@ -509,13 +507,13 @@ class RosettaTestCase(TestCase):
|
||||||
data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'}
|
data = {'m_e48f149a8b2e8baa81b816c0edf93890': 'Hello, world'}
|
||||||
r = self.client.post(self.xx_form_url + '?filter=untranslated', data)
|
r = self.client.post(self.xx_form_url + '?filter=untranslated', data)
|
||||||
# read the result
|
# read the result
|
||||||
with open(self.dest_file, 'rb') as f_:
|
with open(self.dest_file, 'r') as f_:
|
||||||
content = six.text_type(f_.read())
|
content = f_.read()
|
||||||
|
|
||||||
# make sure unicode data was properly converted to ascii
|
# make sure unicode data was properly converted to ascii
|
||||||
self.assertTrue('Hello, world' in content)
|
self.assertTrue('Hello, world' in content)
|
||||||
self.assertTrue('save_header_data@test.com' in content)
|
self.assertTrue('save_header_data@test.com' in content)
|
||||||
self.assertTrue('aeaeae aaaaaaa aaaa uuuu' in content)
|
self.assertTrue('aéaéaé aàaàaàa aâââ üüüü' in content)
|
||||||
|
|
||||||
def test_24_percent_translation(self):
|
def test_24_percent_translation(self):
|
||||||
self.copy_po_file_from_template('./django.po.template')
|
self.copy_po_file_from_template('./django.po.template')
|
||||||
|
|
@ -741,12 +739,8 @@ class RosettaTestCase(TestCase):
|
||||||
|
|
||||||
def test_40_issue_155_auto_compile(self):
|
def test_40_issue_155_auto_compile(self):
|
||||||
def file_hash(file_string):
|
def file_hash(file_string):
|
||||||
if six.PY3:
|
with open(file_string, encoding="latin-1") as file:
|
||||||
with open(file_string, encoding="latin-1") as file:
|
file_content = file.read().encode('utf-8')
|
||||||
file_content = file.read().encode('utf-8')
|
|
||||||
else:
|
|
||||||
with open(file_string) as file:
|
|
||||||
file_content = file.read()
|
|
||||||
return hashlib.md5(file_content).hexdigest()
|
return hashlib.md5(file_content).hexdigest()
|
||||||
|
|
||||||
def message_hashes():
|
def message_hashes():
|
||||||
|
|
@ -886,7 +880,7 @@ class RosettaTestCase(TestCase):
|
||||||
|
|
||||||
# But if the language isn't an option, we get a 404
|
# But if the language isn't an option, we get a 404
|
||||||
with self.settings(
|
with self.settings(
|
||||||
ROSETTA_LANGUAGES=[l for l in settings.LANGUAGES if l[0] != 'xx']
|
ROSETTA_LANGUAGES=[lang for lang, __ in settings.LANGUAGES if lang != 'xx']
|
||||||
):
|
):
|
||||||
view = self._setup_view(
|
view = self._setup_view(
|
||||||
view=views.TranslationFormView(), request=request, **kwargs
|
view=views.TranslationFormView(), request=request, **kwargs
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,16 @@ import hashlib
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
import unicodedata
|
|
||||||
import zipfile
|
import zipfile
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
import six
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import user_passes_test
|
from django.contrib.auth.decorators import user_passes_test
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.http import (
|
from django.http import (Http404, HttpResponse, HttpResponseRedirect,
|
||||||
Http404,
|
JsonResponse)
|
||||||
HttpResponse,
|
|
||||||
HttpResponseRedirect,
|
|
||||||
JsonResponse
|
|
||||||
)
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
|
|
@ -23,8 +19,6 @@ from django.utils.functional import Promise, cached_property
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.decorators.cache import never_cache
|
from django.views.decorators.cache import never_cache
|
||||||
from django.views.generic import TemplateView, View
|
from django.views.generic import TemplateView, View
|
||||||
|
|
||||||
import six
|
|
||||||
from polib import pofile
|
from polib import pofile
|
||||||
|
|
||||||
from . import get_version as get_rosetta_version
|
from . import get_version as get_rosetta_version
|
||||||
|
|
@ -104,7 +98,7 @@ class RosettaFileLevelMixin(RosettaBaseMixin):
|
||||||
"""
|
"""
|
||||||
# (Formerly known as "rosetta_i18n_lang_code")
|
# (Formerly known as "rosetta_i18n_lang_code")
|
||||||
lang_id = self.kwargs['lang_id']
|
lang_id = self.kwargs['lang_id']
|
||||||
if lang_id not in {l[0] for l in rosetta_settings.ROSETTA_LANGUAGES}:
|
if lang_id not in {lang[0] for lang in rosetta_settings.ROSETTA_LANGUAGES}:
|
||||||
raise Http404
|
raise Http404
|
||||||
if not can_translate_language(self.request.user, lang_id):
|
if not can_translate_language(self.request.user, lang_id):
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
@ -227,7 +221,8 @@ class TranslationFileListView(RosettaBaseMixin, TemplateView):
|
||||||
third_party_apps=third_party_apps,
|
third_party_apps=third_party_apps,
|
||||||
)
|
)
|
||||||
po_files = [
|
po_files = [
|
||||||
(get_app_name(l), os.path.realpath(l), pofile(l)) for l in po_paths
|
(get_app_name(lang), os.path.realpath(lang), pofile(lang))
|
||||||
|
for lang in po_paths
|
||||||
]
|
]
|
||||||
po_files.sort(key=lambda app: app[0])
|
po_files.sort(key=lambda app: app[0])
|
||||||
languages.append((language[0], _(language[1]), po_files))
|
languages.append((language[0], _(language[1]), po_files))
|
||||||
|
|
@ -367,15 +362,11 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView):
|
||||||
|
|
||||||
if file_change and self.po_file_is_writable:
|
if file_change and self.po_file_is_writable:
|
||||||
try:
|
try:
|
||||||
self.po_file.metadata['Last-Translator'] = unicodedata.normalize(
|
self.po_file.metadata['Last-Translator'] = "{} {} <{}>".format(
|
||||||
'NFKD',
|
getattr(self.request.user, 'first_name', 'Anonymous'),
|
||||||
u"%s %s <%s>"
|
getattr(self.request.user, 'last_name', 'User'),
|
||||||
% (
|
getattr(self.request.user, 'email', 'anonymous@user.tld'),
|
||||||
getattr(self.request.user, 'first_name', 'Anonymous'),
|
)
|
||||||
getattr(self.request.user, 'last_name', 'User'),
|
|
||||||
getattr(self.request.user, 'email', 'anonymous@user.tld'),
|
|
||||||
),
|
|
||||||
).encode('ascii', 'ignore')
|
|
||||||
self.po_file.metadata['X-Translated-Using'] = u"django-rosetta %s" % (
|
self.po_file.metadata['X-Translated-Using'] = u"django-rosetta %s" % (
|
||||||
get_rosetta_version()
|
get_rosetta_version()
|
||||||
)
|
)
|
||||||
|
|
@ -412,7 +403,7 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView):
|
||||||
import uwsgi
|
import uwsgi
|
||||||
|
|
||||||
uwsgi.reload() # pretty easy right?
|
uwsgi.reload() # pretty easy right?
|
||||||
except:
|
except Exception:
|
||||||
pass # we may not be running under uwsgi :P
|
pass # we may not be running under uwsgi :P
|
||||||
# XXX: It would be nice to add a success message here!
|
# XXX: It would be nice to add a success message here!
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -575,7 +566,7 @@ class TranslationFormView(RosettaFileLevelMixin, TemplateView):
|
||||||
"""
|
"""
|
||||||
ref_lang = self._request_request('ref_lang', 'msgid')
|
ref_lang = self._request_request('ref_lang', 'msgid')
|
||||||
if ref_lang != 'msgid':
|
if ref_lang != 'msgid':
|
||||||
allowed_languages = {l[0] for l in rosetta_settings.ROSETTA_LANGUAGES}
|
allowed_languages = {lang[0] for lang in rosetta_settings.ROSETTA_LANGUAGES}
|
||||||
if ref_lang not in allowed_languages:
|
if ref_lang not in allowed_languages:
|
||||||
raise Http404
|
raise Http404
|
||||||
return ref_lang
|
return ref_lang
|
||||||
|
|
|
||||||
3
tox.ini
3
tox.ini
|
|
@ -42,6 +42,7 @@ deps =
|
||||||
goslate
|
goslate
|
||||||
vcrpy
|
vcrpy
|
||||||
coverage
|
coverage
|
||||||
|
pudb
|
||||||
|
|
||||||
[testenv:gettext]
|
[testenv:gettext]
|
||||||
basepython = python3
|
basepython = python3
|
||||||
|
|
@ -74,6 +75,6 @@ commands=
|
||||||
|
|
||||||
[testenv:flake8]
|
[testenv:flake8]
|
||||||
basepython = python3
|
basepython = python3
|
||||||
deps = flake8==2.4.1
|
deps = flake8==3.9.2
|
||||||
commands=
|
commands=
|
||||||
flake8 {toxinidir}/rosetta
|
flake8 {toxinidir}/rosetta
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue