[damned-lies] Add po file view with inlined diff for fuzzies
- From: Claude Paroz <claudep src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [damned-lies] Add po file view with inlined diff for fuzzies
- Date: Fri, 30 Aug 2019 19:49:35 +0000 (UTC)
commit 66f2339d3810bf31949e6bf921aac1bfe4c78c91
Author: Claude Paroz <claude 2xlibre net>
Date: Fri Aug 30 21:36:06 2019 +0200
Add po file view with inlined diff for fuzzies
common/static/img/diff.png | Bin 0 -> 4858 bytes
templates/vertimus/vertimus_detail.html | 7 ++-
vertimus/urls.py | 3 +
vertimus/views.py | 100 +++++++++++++++++++++++++++++++-
4 files changed, 107 insertions(+), 3 deletions(-)
---
diff --git a/common/static/img/diff.png b/common/static/img/diff.png
new file mode 100644
index 00000000..1e5d1d7a
Binary files /dev/null and b/common/static/img/diff.png differ
diff --git a/templates/vertimus/vertimus_detail.html b/templates/vertimus/vertimus_detail.html
index ae3d3f80..c99050b9 100644
--- a/templates/vertimus/vertimus_detail.html
+++ b/templates/vertimus/vertimus_detail.html
@@ -8,7 +8,7 @@
{% block extrahead %}
<style type="text/css">
tr.tr_author, tr.tr_sync_master { display: none; }
-img#qcheck-icon { width: 24px; height: 24px; margin: 0 0.7em; }
+img.icons { width: 24px; height: 24px; margin: 0 0.7em; }
</style>
<script type="text/javascript" src="{{ STATIC_URL }}js/autosize.min.js"></script>
<script type="text/javascript">
@@ -83,10 +83,13 @@ $(document).ready(function() {
</em></div>
<div style="margin-top: 5px;">
<a class="btn btn-action" href="{{ po_url }}" title="{% trans 'Download PO file' %}"><img src="{{
STATIC_URL }}img/download.png" alt="{% trans 'Download PO file' %}"></a>
+ {% if stats.fuzzy %}
+ <a href="{% url 'stats-msgiddiff' stats.pk %}" title="{% trans 'PO file with inline diffs for fuzzy
strings' %}"><img class="icons" src="{{ STATIC_URL }}img/diff.png"></a>
+ {% endif %}
{% if domain.dtype == 'doc' %}
<a href="{% url 'stats-quality-check' stats.pk %}" data-target="#modal-container"
role="button" data-toggle="modal" title="{% trans 'Quality checks' %}">
- <img id="qcheck-icon" src="{{ STATIC_URL }}img/qa-icon.svg" alt="quality check icon"></a>
+ <img class="icons" src="{{ STATIC_URL }}img/qa-icon.svg" alt="quality check icon"></a>
{% endif %}
{% trans "PO file statistics:" %} <br>
<div id="stats_po">
diff --git a/vertimus/urls.py b/vertimus/urls.py
index 90c7c6ce..f35c3224 100644
--- a/vertimus/urls.py
+++ b/vertimus/urls.py
@@ -32,6 +32,9 @@ urlpatterns = [
path('stats/<int:stats_pk>/qcheck/',
views.QualityCheckView.as_view(),
name='stats-quality-check'),
+ path('stats/<int:stats_pk>/msgiddiff/',
+ views.MsgiddiffView.as_view(),
+ name='stats-msgiddiff'),
path('action/<int:action_pk>/build_help/',
views.BuildTranslatedDocsView.as_view(),
name='action-build-help'),
diff --git a/vertimus/views.py b/vertimus/views.py
index 502b0776..956db3f2 100644
--- a/vertimus/views.py
+++ b/vertimus/views.py
@@ -1,4 +1,5 @@
import difflib
+import html
import os
import re
import shutil
@@ -9,7 +10,7 @@ from xml.dom.minidom import parse
from django.conf import settings
from django.contrib import messages
-from django.http import HttpResponseRedirect, Http404
+from django.http import HttpResponseRedirect, Http404, StreamingHttpResponse
from django.shortcuts import render, get_object_or_404
from django.urls import reverse
from django.utils.html import escape
@@ -298,6 +299,74 @@ class QualityCheckView(PoFileActionBase):
return context
+class MsgiddiffView(PoFileActionBase):
+ HEADER = '''
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <style>
+ body { font-family: monospace; }
+ div.warning { font-size: 200%; padding: 3em; background-color: #ddd; }
+ div.diff { color: #444; background-color: #eee; padding: 4px; width: 78ch; line-height: 1.4;
margin-left: 2.5em; }
+ div.nowrap { white-space: pre; }
+ span.noline { color: #aaa; }
+ del { color: red; background-color: #fed4d4; }
+ ins { color: green; background-color: #c8f5c8; }
+ </style>
+ </head>
+<body>
+''' + '<div class="warning">{}</div>'.format(
+ _(
+ "WARNING: This file is <b>NOT</b> suitable as a base for completing this translation. "
+ "It contains HTML markup to highlight differential parts of changed strings."
+ ))
+ FOOTER = '</body</html>'
+
+ def streamed_file(self, po_file):
+ def strip(line):
+ if line.startswith('#| '):
+ line = line[3:]
+ if line.startswith('msgid '):
+ line = line[6:]
+ return line.rstrip('\n').strip('"')
+
+ yield self.HEADER
+ prev_id = curr_id = None
+ no_wrap = False
+ with open(po_file, 'r') as fh:
+ for noline, line in enumerate(fh.readlines(), start=1):
+ if prev_id is not None:
+ if line.startswith('#|'):
+ prev_id.append(line)
+ continue
+ elif line.startswith('msgstr "'):
+ # Compute and display
+ sep = '\n' if no_wrap else ''
+ yield (
+ '<div class="diff%s">' % (' nowrap' if no_wrap else '') + diff_strings(
+ html.escape(sep.join(strip(l) for l in prev_id)),
+ html.escape(sep.join(strip(l) for _, l in curr_id))
+ ) + '</div>'
+ )
+ for _noline, _line in curr_id:
+ yield '<span class="noline">%d</span> ' % _noline + html.escape(_line) + '<br>'
+ prev_id = None
+ else:
+ curr_id.append((noline, line))
+ continue
+ if line.startswith('#, fuzzy'):
+ prev_id = []
+ curr_id = []
+ no_wrap = 'no-wrap' in line
+ yield '<span class="noline">%d</span> ' % noline + html.escape(line) + '<br>'
+ yield self.FOOTER
+
+ def get(self, request, *args, **kwargs):
+ stats = get_object_or_404(Statistics, pk=self.kwargs['stats_pk'])
+ pofile = stats.po_path()
+ return StreamingHttpResponse(self.streamed_file(pofile))
+
+
class BuildTranslatedDocsView(PoFileActionBase):
http_method_names = ['post']
@@ -388,3 +457,32 @@ class BuildTranslatedDocsView(PoFileActionBase):
html_name = '%s.html' % base_name
(html_dir / 'index.html').symlink_to(html_dir / html_name)
return ''
+
+
+def diff_strings(previous, current):
+ """
+ Compute a diff between two strings, with inline differences.
+ http://stackoverflow.com/questions/774316/python-difflib-highlighting-differences-inline
+ >>> diff_strings(old string, new string)
+ 'lorem<ins> foo</ins> ipsum dolor <del>sit </del>amet'
+ """
+ if not previous or not current:
+ return ''
+ seqm = difflib.SequenceMatcher(
+ a=previous.replace('\r\n', '\n'),
+ b=current.replace('\r\n', '\n'),
+ )
+ output= []
+ for opcode, a0, a1, b0, b1 in seqm.get_opcodes():
+ if opcode == 'equal':
+ output.append(seqm.a[a0:a1])
+ elif opcode == 'insert':
+ output.append('<ins title="New text">' + seqm.b[b0:b1] + "</ins>")
+ elif opcode == 'delete':
+ output.append('<del title="Deleted text">' + seqm.a[a0:a1] + "</del>")
+ elif opcode == 'replace':
+ output.append('<del title="Deleted text">' + seqm.a[a0:a1] + "</del>")
+ output.append('<ins title="New text">' + seqm.b[b0:b1] + "</ins>")
+ else:
+ raise RuntimeError("unexpected opcode")
+ return mark_safe(''.join(output))
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]