File indexing completed on 2024-12-08 05:09:21

0001 # -*- coding: UTF-8 -*-
0002 
0003 """
0004 Check language of translation using LanguageTool.
0005 
0006 Documented in C{doc/user/sieving.docbook}.
0007 
0008 @author: Sébastien Renard <sebastien.renard@digitalfox.org>
0009 @license: GPLv3
0010 """
0011 
0012 import json
0013 from http.client import HTTPConnection
0014 import socket
0015 import sys
0016 from urllib.parse import urlencode
0017 from xml.dom.minidom import parseString
0018 
0019 from pology import _, n_
0020 from pology.colors import cjoin
0021 from pology.msgreport import report_msg_to_lokalize, warning_on_msg
0022 from pology.report import report, warning
0023 from pology.sieve import SieveError, SieveCatalogError
0024 from pology.sieve import add_param_lang, add_param_accel, add_param_markup
0025 from pology.sieve import add_param_filter
0026 from pology.getfunc import get_hook_ireq
0027 from pology.sieve import add_param_poeditors
0028 
0029 
0030 _REQUEST="/v2/check?language=%s&disabledRules=%s&%s"
0031 
0032 def setup_sieve (p):
0033 
0034     p.set_desc(_("@info sieve discription",
0035     "Check language of translation using LanguageTool."
0036     "\n\n"
0037     "LanguageTool (http://www.languagetool.org) is an open source "
0038     "language checker, which may be used as a standalone application, "
0039     "or in server-client mode. "
0040     "This sieve runs in client-server mode, so make sure Language Tool "
0041     "is running before this sieve is run."
0042     ))
0043 
0044     add_param_lang(p)
0045     add_param_accel(p)
0046     add_param_markup(p)
0047     add_param_filter(p,
0048         intro=_("@info sieve parameter discription",
0049         "The F1A or F3A/C hook through which to filter the translation "
0050         "before passing it to grammar checking."
0051         ))
0052     p.add_param("host", str, defval="localhost",
0053                 metavar=_("@info sieve parameter value placeholder", "NAME"),
0054                 desc=_("@info sieve parameter discription",
0055     "Name of the host where the server is running."
0056     ))
0057     p.add_param("port", str, defval="8081",
0058                 metavar=_("@info sieve parameter value placeholder", "NUMBER"),
0059                 desc=_("@info sieve parameter discription",
0060     "TCP port on the host which server uses to listen for queries."
0061     ))
0062                 
0063     add_param_poeditors(p)
0064 
0065 
0066 class Sieve (object):
0067 
0068     def __init__ (self, params):
0069 
0070         self.nmatch = 0 # Number of match for finalize
0071         self.connection=None # Connection to LanguageTool server
0072 
0073         self.setLang=params.lang
0074         self.setAccel=params.accel
0075         self.setMarkup=params.markup
0076         self.lokalize = params.lokalize
0077 
0078         # LanguageTool server parameters.
0079         host=params.host
0080         port=params.port
0081         #TODO: autodetect tcp port by reading LanguageTool config file if host is localhost
0082 
0083         # As LT server does not seem to read disabled rules from his config file, we manage exception here
0084         #TODO: investigate deeper this problem and make a proper bug report to LT devs.
0085         self.disabledRules=["HUNSPELL_RULE","UPPERCASE_SENTENCE_START","COMMA_PARENTHESIS_WHITESPACE"]
0086 
0087         # Create connection to the LanguageTool server
0088         self.connection=HTTPConnection(host, port)
0089 
0090         self.pfilters = [[get_hook_ireq(x, abort=True), x]
0091                          for x in (params.filter or [])]
0092 
0093 
0094     def process_header (self, hdr, cat):
0095 
0096         self.lang=(self.setLang or cat.language())
0097         if not self.lang:
0098             raise SieveCatalogError(
0099                 _("@info",
0100                   "Cannot determine language for catalog '%(file)s'.",
0101                   file=cat.filename))
0102 
0103         # Force explicitly given accelerators and markup.
0104         if self.setAccel is not None:
0105             cat.set_accelerator(self.setAccel)
0106         if self.setMarkup is not None:
0107             cat.set_markup(self.setMarkup)
0108 
0109 
0110     def process (self, msg, cat):
0111 
0112         if msg.obsolete:
0113             return
0114 
0115         try:
0116             for msgstr in msg.msgstr:
0117 
0118                 # Apply precheck filters.
0119                 for pfilter, pfname in self.pfilters:
0120                     try: # try as type F1A hook
0121                         msgstr = pfilter(msgstr)
0122                     except TypeError:
0123                         try: # try as type F3* hook
0124                             msgstr = pfilter(msgstr, msg, cat)
0125                         except TypeError:
0126                             raise SieveError(
0127                                 _("@info",
0128                                   "Cannot execute filter '%(filt)s'.",
0129                                   filt=pfname))
0130 
0131                 self.connection.request(
0132                     "GET",
0133                     _REQUEST % (
0134                         self.lang,
0135                         ",".join(self.disabledRules),
0136                         urlencode({"text":msgstr.encode("UTF-8")}),
0137                     )
0138                 )
0139                 response=self.connection.getresponse()
0140                 if not response:
0141                     continue
0142                 matches = json.loads(response.read())["matches"]
0143                 if not matches:
0144                     continue
0145                 for match in matches:
0146                     self.nmatch+=1
0147                     report("-"*(len(msgstr)+8))
0148                     report(_("@info",
0149                                 "<bold>%(file)s:%(line)d(#%(entry)d)</bold>",
0150                                 file=cat.filename, line=msg.refline, entry=msg.refentry))
0151                     #TODO: create a report function in the right place
0152                     #TODO: color in red part of context that make the mistake
0153                     report(_("@info",
0154                                 "<bold>Context:</bold> %(snippet)s",
0155                                 snippet=match["context"]["text"]))
0156                     report(_("@info",
0157                                 "(%(rule)s) <bold><red>==></red></bold> %(note)s",
0158                                 rule=match["rule"]["id"],
0159                                 note=match["message"]))
0160                     report("")
0161                     if self.lokalize:
0162                         repls = [_("@label", "Grammar errors:")]
0163                         repls.append(_(
0164                             "@info",
0165                             "<bold>%(file)s:%(line)d(#%(entry)d)</bold>",
0166                             file=cat.filename,
0167                             line=msg.refline,
0168                             entry=msg.refentry
0169                         ))
0170                         repls.append(_(
0171                             "@info",
0172                             "(%(rule)s) <bold><red>==></red></bold> %(note)s",
0173                             rule=match["rule"]["id"],
0174                             note=match["message"]
0175                         ))
0176                         report_msg_to_lokalize(msg, cat, cjoin(repls, "\n"))
0177         except socket.error:
0178             raise SieveError(_("@info",
0179                                "Cannot connect to LanguageTool server. "
0180                                "Did you start it?"))
0181 
0182 
0183     def finalize (self):
0184         if self.nmatch:
0185             msg = n_("@info:progress",
0186                      "Detected %(num)d problem in grammar and style.",
0187                      "Detected %(num)d problems in grammar and style.",
0188                      num=self.nmatch)
0189             report("===== " + msg)
0190