From 59f0beeba637c46a10378fba80411fcc863c2647 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 18 Apr 2023 18:17:33 +0200 Subject: [PATCH] Remove the bundled lupdate6 tool and use the PyQt6 package --- i18n/pylupdate6/README.md | 7 - i18n/pylupdate6/__init__.py | 22 -- i18n/pylupdate6/lupdate.py | 90 ------- i18n/pylupdate6/python_source.py | 362 -------------------------- i18n/pylupdate6/source_file.py | 29 --- i18n/pylupdate6/translation_file.py | 390 ---------------------------- i18n/pylupdate6/translations.py | 51 ---- i18n/pylupdate6/user.py | 47 ---- setup.py | 11 +- setup/debian/copyright | 16 -- 10 files changed, 7 insertions(+), 1018 deletions(-) delete mode 100644 i18n/pylupdate6/README.md delete mode 100644 i18n/pylupdate6/__init__.py delete mode 100644 i18n/pylupdate6/lupdate.py delete mode 100644 i18n/pylupdate6/python_source.py delete mode 100644 i18n/pylupdate6/source_file.py delete mode 100644 i18n/pylupdate6/translation_file.py delete mode 100644 i18n/pylupdate6/translations.py delete mode 100644 i18n/pylupdate6/user.py diff --git a/i18n/pylupdate6/README.md b/i18n/pylupdate6/README.md deleted file mode 100644 index 32ffae95..00000000 --- a/i18n/pylupdate6/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# PyQt6 Linguist Tool - -This is a copy of the `pylupdate` tool from PyQt6 version 6.2.1 from -[PyPi](https://pypi.org/project/PyQt6/). -The tool is stripped down to run for novelWriter. - -PyQt6 is licensed under GPL 3.0 diff --git a/i18n/pylupdate6/__init__.py b/i18n/pylupdate6/__init__.py deleted file mode 100644 index 698832fc..00000000 --- a/i18n/pylupdate6/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2021 Riverbank Computing Limited -# -# This file is part of PyQt6. -# -# This file may be used under the terms of the GNU General Public License -# version 3.0 as published by the Free Software Foundation and appearing in -# the file LICENSE included in the packaging of this file. Please review the -# following information to ensure the GNU General Public License version 3.0 -# requirements will be met: http://www.gnu.org/copyleft/gpl.html. -# -# If you do not wish to use this file under the terms of the GPL version 3.0 -# then you may purchase a commercial license. For more information contact -# info@riverbankcomputing.com. -# -# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - - -# The public API. -from .lupdate import lupdate - -__all__ = ["lupdate"] diff --git a/i18n/pylupdate6/lupdate.py b/i18n/pylupdate6/lupdate.py deleted file mode 100644 index c624256a..00000000 --- a/i18n/pylupdate6/lupdate.py +++ /dev/null @@ -1,90 +0,0 @@ -# Copyright (c) 2021 Riverbank Computing Limited -# -# This file is part of PyQt6. -# -# This file may be used under the terms of the GNU General Public License -# version 3.0 as published by the Free Software Foundation and appearing in -# the file LICENSE included in the packaging of this file. Please review the -# following information to ensure the GNU General Public License version 3.0 -# requirements will be met: http://www.gnu.org/copyleft/gpl.html. -# -# If you do not wish to use this file under the terms of the GPL version 3.0 -# then you may purchase a commercial license. For more information contact -# info@riverbankcomputing.com. -# -# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - - -import fnmatch -import os - -# from .designer_source import DesignerSource -from .python_source import PythonSource -from .translation_file import TranslationFile -from .user import UserException - - -def lupdate( - sources, translation_files, no_obsolete=False, no_summary=True, verbose=False, excludes=None -): - """ Update a sequence of translation (.ts) files from a sequence of Python - source (.py) files, Designer source (.ui) files or directories containing - source files. - """ - - if excludes is None: - excludes = () - - # Read the .ts files. - translations = [ - TranslationFile(ts, no_obsolete=no_obsolete, no_summary=no_summary, verbose=verbose) - for ts in translation_files - ] - - # Read the sources. - source_files = [] - for source in sources: - if os.path.isdir(source): - for dirpath, _, filenames in os.walk(source): - for fn in filenames: - # Apply any exclusion patterns. - for exclude in excludes: - if fnmatch.fnmatch(fn, exclude): - fn = None - break - - if fn is None: - continue - - filename = os.path.join(dirpath, fn) - - if filename.endswith('.py'): - source_files.append(PythonSource(filename=filename, verbose=verbose)) - - # UI file support is disabled - # elif filename.endswith('.ui'): - # source_files.append( - # DesignerSource(filename=filename, - # verbose=verbose)) - - elif verbose: - print("Ignoring", filename) - - elif source.endswith('.py'): - source_files.append(PythonSource(filename=source, verbose=verbose)) - - # UI file support is disabled - # elif source.endswith('.ui'): - # source_files.append( - # DesignerSource(filename=source, verbose=verbose)) - - else: - raise UserException("{0} must be a directory or a .py or a .ui file".format(source)) - - # Update each translation for each source. - for t in translations: - for s in source_files: - t.update(s) - - t.write() diff --git a/i18n/pylupdate6/python_source.py b/i18n/pylupdate6/python_source.py deleted file mode 100644 index 6e01895c..00000000 --- a/i18n/pylupdate6/python_source.py +++ /dev/null @@ -1,362 +0,0 @@ -# Copyright (c) 2021 Riverbank Computing Limited -# -# This file is part of PyQt6. -# -# This file may be used under the terms of the GNU General Public License -# version 3.0 as published by the Free Software Foundation and appearing in -# the file LICENSE included in the packaging of this file. Please review the -# following information to ensure the GNU General Public License version 3.0 -# requirements will be met: http://www.gnu.org/copyleft/gpl.html. -# -# If you do not wish to use this file under the terms of the GPL version 3.0 -# then you may purchase a commercial license. For more information contact -# info@riverbankcomputing.com. -# -# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - - -import ast -import re -import tokenize - -from .source_file import SourceFile -from .translations import Context, EmbeddedComments, Message -from .user import User, UserException - - -class PythonSource(SourceFile, User): - """ Encapsulate a Python source file. """ - - # The regular expression to extract a PEP 263 encoding. - _PEP_263 = re.compile(rb'^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)') - - def __init__(self, **kwargs): - """ Initialise the object. """ - - super().__init__(**kwargs) - - # Read the source file. - self.progress("Reading {0}...".format(self.filename)) - with open(self.filename, 'rb') as f: - source = f.read() - - # Implement universal newlines. - source = source.replace(b'\r\n', b'\n').replace(b'\r', b'\n') - - # Try and extract a PEP 263 encoding. - encoding = 'UTF-8' - - for line_nr, line in enumerate(source.split(b'\n')): - if line_nr > 1: - break - - match = re.match(self._PEP_263, line) - if match: - encoding = match.group(1).decode('ascii') - break - - # Decode the source according to the encoding. - try: - source = source.decode(encoding) - except LookupError: - raise UserException("Unsupported encoding '{0}'".format(encoding)) - - # Parse the source file. - self.progress("Parsing {0}...".format(self.filename)) - - try: - tree = ast.parse(source, filename=self.filename) - except SyntaxError as e: - raise UserException( - "Invalid syntax at line {0} of {1}:\n{2}".format( - e.lineno, e.filename, e.text.rstrip() - ) - ) - - # Look for translation contexts and their contents. - visitor = Visitor(self) - visitor.visit(tree) - - # Read the file again as a sequence of tokens so that we see the - # comments. - with open(self.filename, 'rb') as f: - current = None - - for token in tokenize.tokenize(f.readline): - if token.type == tokenize.COMMENT: - # See if it is an embedded comment. - parts = token.string.split(' ', maxsplit=1) - if len(parts) == 2: - if parts[0] == '#:': - if current is None: - current = EmbeddedComments() - - current.extra_comments.append(parts[1]) - elif parts[0] == '#=': - if current is None: - current = EmbeddedComments() - - current.message_id = parts[1] - elif parts[0] == '#~': - parts = parts[1].split(' ', maxsplit=1) - if len(parts) == 1: - parts.append('') - - if current is None: - current = EmbeddedComments() - - current.extras.append(parts) - - elif token.type == tokenize.NL: - continue - - elif current is not None: - # Associate the embedded comment with the line containing - # this token. - line_nr = token.start[0] - - # See if there is a message on that line. - for context in self.contexts: - for message in context.messages: - if message.line_nr == line_nr: - break - else: - message = None - - if message is not None: - message.embedded_comments = current - break - - current = None - - -class Visitor(ast.NodeVisitor): - """ A visitor that extracts translation contexts. """ - - def __init__(self, source): - """ Initialise the visitor. """ - - self._source = source - self._context_stack = [] - - super().__init__() - - def visit_Call(self, node): - """ Visit a call. """ - - # Parse the arguments if a translation function is being called. - call_args = None - - if isinstance(node.func, ast.Attribute): - name = node.func.attr - - if name == 'tr': - call_args = self._parse_tr(node) - elif name == 'translate': - call_args = self._parse_translate(node) - - elif isinstance(node.func, ast.Name): - name = node.func.id - - if name == 'QT_TR_NOOP': - call_args = self._parse_QT_TR_NOOP(node) - elif name == 'QT_TRANSLATE_NOOP': - call_args = self._parse_QT_TRANSLATE_NOOP(node) - - # Update the context if the arguments are usable. - if call_args is not None and call_args.source != '': - call_args.context.messages.append( - Message( - self._source.filename, node.lineno, - call_args.source, call_args.disambiguation, - (call_args.numerus) - ) - ) - - self.generic_visit(node) - - def visit_ClassDef(self, node): - """ Visit a class. """ - - try: - name = self._context_stack[-1].name + '.' + node.name - except IndexError: - name = node.name - - self._context_stack.append(Context(name)) - - self.generic_visit(node) - - context = self._context_stack.pop() - - if context.messages: - self._source.contexts.append(context) - - def _get_current_context(self): - """ Return the current Context object if there is one. """ - - return self._context_stack[-1] if self._context_stack else None - - @classmethod - def _get_first_str(cls, args): - """ Get the first of a list of arguments as a str. """ - - # Check that there is at least one argument. - if not args: - return None - - return cls._get_str(args[0]) - - def _get_or_create_context(self, name): - """ Return the Context object for a name, creating it if necessary. """ - - for context in self._source.contexts: - if context.name == name: - return context - - context = Context(name) - self._source.contexts.append(context) - - return context - - @staticmethod - def _get_str(node, allow_none=False): - """ Return the str from a node or None if it wasn't an appropriate - node. - """ - - if isinstance(node, ast.Str): - return node.s - - if isinstance(node, ast.Constant): - if isinstance(node.value, str): - return node.value - - if allow_none and node.value is None: - return '' - - return None - - def _parse_QT_TR_NOOP(self, node): - """ Parse the arguments to QT_TR_NOOP(). """ - - # Ignore unless there is a current context. - context = self._get_current_context() - if context is None: - return None - - call_args = self._parse_noop_without_context(node.args, node.keywords) - if call_args is None: - return None - - call_args.context = context - - return call_args - - def _parse_QT_TRANSLATE_NOOP(self, node): - """ Parse the arguments to QT_TRANSLATE_NOOP(). """ - - # Get the context. - name = self._get_first_str(node.args) - if name is None: - return None - - call_args = self._parse_noop_without_context(node.args[1:], node.keywords) - if call_args is None: - return None - - call_args.context = self._get_or_create_context(name) - - return call_args - - def _parse_tr(self, node): - """ Parse the arguments to tr(). """ - - # Ignore unless there is a current context. - context = self._get_current_context() - if context is None: - return None - - call_args = self._parse_without_context(node.args, node.keywords) - if call_args is None: - return None - - call_args.context = context - - return call_args - - def _parse_translate(self, node): - """ Parse the arguments to translate(). """ - - # Get the context. - name = self._get_first_str(node.args) - if name is None: - return None - - call_args = self._parse_without_context(node.args[1:], node.keywords) - if call_args is None: - return None - - call_args.context = self._get_or_create_context(name) - - return call_args - - def _parse_without_context(self, args, keywords): - """ Parse arguments for a message source and optional disambiguation - and n. - """ - - # The source is required. - source = self._get_first_str(args) - if source is None: - return None - - if len(args) > 1: - disambiguation = self._get_str(args[1], allow_none=True) - else: - for kw in keywords: - if kw.arg == 'disambiguation': - disambiguation = self._get_str(kw.value, allow_none=True) - break - else: - disambiguation = '' - - # Ignore if the disambiguation is specified but isn't a string. - if disambiguation is None: - return None - - if len(args) > 2: - numerus = True - else: - numerus = 'n' in keywords - - if len(args) > 3: - return None - - return CallArguments(source, disambiguation, numerus) - - def _parse_noop_without_context(self, args, keywords): - """ Parse arguments for a message source. """ - - # There must be exactly one positional argument. - if len(args) != 1 or len(keywords) != 0: - return None - - source = self._get_str(args[0]) - if source is None: - return None - - return CallArguments(source) - - -class CallArguments: - """ Encapsulate the possible arguments of a translation function. """ - - def __init__(self, source, disambiguation='', numerus=False): - """ Initialise the object. """ - - self.context = None - self.source = source - self.disambiguation = disambiguation - self.numerus = numerus diff --git a/i18n/pylupdate6/source_file.py b/i18n/pylupdate6/source_file.py deleted file mode 100644 index 3cbbea29..00000000 --- a/i18n/pylupdate6/source_file.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2021 Riverbank Computing Limited -# -# This file is part of PyQt6. -# -# This file may be used under the terms of the GNU General Public License -# version 3.0 as published by the Free Software Foundation and appearing in -# the file LICENSE included in the packaging of this file. Please review the -# following information to ensure the GNU General Public License version 3.0 -# requirements will be met: http://www.gnu.org/copyleft/gpl.html. -# -# If you do not wish to use this file under the terms of the GPL version 3.0 -# then you may purchase a commercial license. For more information contact -# info@riverbankcomputing.com. -# -# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - - -class SourceFile: - """ The base class for any source file that provides translation contexts. - """ - - def __init__(self, filename, **kwargs): - """ Initialise the object. """ - - super().__init__(**kwargs) - - self.filename = filename - self.contexts = [] diff --git a/i18n/pylupdate6/translation_file.py b/i18n/pylupdate6/translation_file.py deleted file mode 100644 index ba71513c..00000000 --- a/i18n/pylupdate6/translation_file.py +++ /dev/null @@ -1,390 +0,0 @@ -# Copyright (c) 2021 Riverbank Computing Limited -# -# This file is part of PyQt6. -# -# This file may be used under the terms of the GNU General Public License -# version 3.0 as published by the Free Software Foundation and appearing in -# the file LICENSE included in the packaging of this file. Please review the -# following information to ensure the GNU General Public License version 3.0 -# requirements will be met: http://www.gnu.org/copyleft/gpl.html. -# -# If you do not wish to use this file under the terms of the GPL version 3.0 -# then you may purchase a commercial license. For more information contact -# info@riverbankcomputing.com. -# -# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - - -import os -from xml.etree import ElementTree - -from .user import User, UserException - - -class TranslationFile(User): - """ Encapsulate a translation file. """ - - def __init__(self, ts_file, no_obsolete, no_summary, **kwargs): - """ Initialise the translation file. """ - - super().__init__(**kwargs) - - if os.path.isfile(ts_file): - self.progress("Reading {0}...".format(ts_file)) - - try: - self._root = ElementTree.parse(ts_file).getroot() - except Exception as e: - raise UserException( - "{}: {}: {}".format(ts_file, "invalid translation file", str(e)) - ) - else: - self._root = ElementTree.fromstring(_EMPTY_TS) - - self._ts_file = ts_file - self._no_obsolete = no_obsolete - self._no_summary = no_summary - self._updated_contexts = {} - - # Create a dict of contexts keyed by the context name and having the - # list of message elements as the value. - self._contexts = {} - - # Also create a dict of existing translations so that they can be - # re-used. - self._translations = {} - - context_els = [] - for context_el in self._root: - if context_el.tag != 'context': - continue - - context_els.append(context_el) - - name = '' - message_els = [] - - for el in context_el: - if el.tag == 'name': - name = el.text - elif el.tag == 'message': - message_els.append(el) - - if name: - self._contexts[name] = message_els - - for message_el in message_els: - source_el = message_el.find('source') - if source_el is None or not source_el.text: - continue - - translation_el = message_el.find('translation') - if translation_el is None or not translation_el.text: - continue - - self._translations[source_el.text] = translation_el.text - - # Remove the context elements but keep everything else in the root - # (probably set by Linguist). - for context_el in context_els: - self._root.remove(context_el) - - # Clear the summary statistics. - self._nr_new = 0 - self._nr_new_duplicates = 0 - self._nr_new_using_existing_translation = 0 - self._nr_existing = 0 - self._nr_kept_obsolete = 0 - self._nr_discarded_obsolete = 0 - self._nr_discarded_untranslated = 0 - - # Remember all new messages so we can make the summary less confusing - # than it otherwise might be. - self._new_message_els = [] - - def update(self, source): - """ Update the translation file from a SourceFile object. """ - - self.progress("Updating {0} from {1}...".format(self._ts_file, source.filename)) - - for context in source.contexts: - # Get the messages that we already know about for this context. - try: - message_els = self._contexts[context.name] - except KeyError: - message_els = [] - - # Get the messages that have already been updated. - updated_message_els = self._get_updated_message_els(context.name) - - for message in context.messages: - message_el = self._find_message(message, message_els) - - if message_el is not None: - # Move the message to the updated list. - message_els.remove(message_el) - self._add_message_el(message_el, updated_message_els) - else: - # See if this is a new message. If not then we just have - # another location for an existing message. - message_el = self._find_message(message, updated_message_els) - - if message_el is None: - message_el = self._make_message_el(message) - updated_message_els.append(message_el) - - self.progress("Added new message '{0}'".format(self.pretty(message.source))) - self._nr_new += 1 - else: - self.progress("Updated message '{0}'".format(self.pretty(message.source))) - - # Don't count another copy of a new message as an existing - # one. - if message_el in self._new_message_els: - self._nr_new_duplicates += 1 - else: - self._nr_existing += 1 - - message_el.insert(0, self._make_location_el(message)) - - def write(self): - """ Write the translation file back to the filesystem. """ - - # If we are keeping obsolete messages then add them to the updated - # message elements list. - for name, message_els in self._contexts.items(): - updated_message_els = None - - for message_el in message_els: - source = self.pretty(message_el.find('source').text) - - translation_el = message_el.find('translation') - if translation_el is not None and translation_el.text: - if self._no_obsolete: - self.progress("Discarded obsolete message '{0}'".format(source)) - self._nr_discarded_obsolete += 1 - else: - translation_el.set('type', 'vanished') - - if updated_message_els is None: - updated_message_els = self._get_updated_message_els(name) - - self._add_message_el(message_el, updated_message_els) - - self.progress("Kept obsolete message '{0}'".format(source)) - self._nr_kept_obsolete += 1 - else: - self.progress("Discarded untranslated message '{0}'".format(source)) - self._nr_discarded_untranslated += 1 - - # Created the sorted context elements. - for name in sorted(self._updated_contexts.keys()): - context_el = ElementTree.Element('context') - - name_el = ElementTree.Element('name') - name_el.text = name - context_el.append(name_el) - - context_el.extend(self._updated_contexts[name]) - - self._root.append(context_el) - - self.progress("Writing {0}...".format(self._ts_file)) - with open(self._ts_file, 'w', encoding='utf-8', newline='\n') as f: - f.write('\n') - f.write('\n') - - # Python v3.9 and later. - if hasattr(ElementTree, 'indent'): - ElementTree.indent(self._root) - - ElementTree.ElementTree(self._root).write(f, encoding='unicode') - f.write('\n') - - if not self._no_summary: - self._summary() - - @staticmethod - def _add_message_el(message_el, updated_message_els): - """ Add a message element to a list of updated message elements. """ - - # Remove all the location elements. - for location_el in message_el.findall('location'): - message_el.remove(location_el) - - # Add the message to the updated list. - updated_message_els.append(message_el) - - @classmethod - def _find_message(cls, message, message_els): - """ Return the message element for a message from a list. """ - - for message_el in message_els: - source = '' - comment = '' - extra_comment = '' - extras = [] - - # Extract the data from the element. - for el in message_el: - if el.tag == 'source': - source = el.text - elif el.tag == 'comment': - comment = el.text - elif el.tag == 'extracomment': - extra_comment = el.text - elif el.tag.startswith('extra-'): - extras.append([el.tag[6:], el.text]) - - # Compare with the message. - if source != message.source: - continue - - if comment != message.comment: - continue - - if extra_comment != cls._get_message_extra_comments(message): - continue - - if extras != message.embedded_comments.extras: - continue - - return message_el - - return None - - @staticmethod - def _get_message_extra_comments(message): - """ Return a message's extra comments as they appear in a .ts file. """ - - return ' '.join(message.embedded_comments.extra_comments) - - def _get_updated_message_els(self, name): - """ Return the list of updated message elements for a context. """ - - try: - updated_message_els = self._updated_contexts[name] - except KeyError: - updated_message_els = [] - self._updated_contexts[name] = updated_message_els - - return updated_message_els - - def _make_location_el(self, message): - """ Return a 'location' element. """ - - return ElementTree.Element( - 'location', - filename=os.path.relpath( - message.filename, start=os.path.dirname(os.path.abspath(self._ts_file)) - ), - line=str(message.line_nr) - ) - - def _make_message_el(self, message): - """ Return a 'message' element. """ - - attrs = {} - - if message.embedded_comments.message_id: - attrs['id'] = message.embedded_comments.message_id - - if message.numerus: - attrs['numerus'] = 'yes' - - message_el = ElementTree.Element('message', attrs) - - source_el = ElementTree.Element('source') - source_el.text = message.source - message_el.append(source_el) - - if message.comment: - comment_el = ElementTree.Element('comment') - comment_el.text = message.comment - message_el.append(comment_el) - - if message.embedded_comments.extra_comments: - extracomment_el = ElementTree.Element('extracomment') - extracomment_el.text = self._get_message_extra_comments(message) - message_el.append(extracomment_el) - - translation_el = ElementTree.Element('translation', type='unfinished') - - # Try and find another message with the same source and use its - # translation if it has one. - translation = self._translations.get(message.source) - if translation: - translation_el.text = translation - - self.progress("Reused existing translation for '{0}'".format( - self.pretty(message.source)) - ) - self._nr_new_using_existing_translation += 1 - - if message.numerus: - translation_el.append(ElementTree.Element('numerusform')) - - message_el.append(translation_el) - - for field, value in message.embedded_comments.extras: - el = ElementTree.Element('extra-' + field) - el.text = value - message_el.append(el) - - self._new_message_els.append(message_el) - - return message_el - - def _summary(self): - """ Display the summary of changes to the user. """ - - summary_lines = [] - - # Display a line of the summary and the heading if not already done. - def summary(line): - nonlocal summary_lines - - if not summary_lines: - summary_lines.append("Summary of changes to {ts}:".format(ts=self._ts_file)) - - summary_lines.append(" " + line) - - if self._nr_new: - if self._nr_new_duplicates: - summary("{0} new messages were added (and {1} duplicates)".format( - self._nr_new, self._nr_new_duplicates)) - else: - summary("{0} new messages were added".format(self._nr_new)) - - if self._nr_new_using_existing_translation: - summary("{0} messages reused existing translations".format( - self._nr_new_using_existing_translation)) - - if self._nr_existing: - summary("{0} existing messages were found".format( - self._nr_existing)) - - if self._nr_kept_obsolete: - summary("{0} obsolete messages were kept".format( - self._nr_kept_obsolete)) - - if self._nr_discarded_obsolete: - summary("{0} obsolete messages were discarded".format( - self._nr_discarded_obsolete)) - - if self._nr_discarded_untranslated: - summary("{0} untranslated messages were discarded".format( - self._nr_discarded_untranslated)) - - if not summary_lines: - summary_lines.append("{ts} was unchanged".format(ts=self._ts_file)) - - print(os.linesep.join(summary_lines)) - - -# The XML of an empty .ts file. This is what a current lupdate will create -# with an empty C++ source file. -_EMPTY_TS = ''' - -''' diff --git a/i18n/pylupdate6/translations.py b/i18n/pylupdate6/translations.py deleted file mode 100644 index fa9bdf81..00000000 --- a/i18n/pylupdate6/translations.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (c) 2021 Riverbank Computing Limited -# -# This file is part of PyQt6. -# -# This file may be used under the terms of the GNU General Public License -# version 3.0 as published by the Free Software Foundation and appearing in -# the file LICENSE included in the packaging of this file. Please review the -# following information to ensure the GNU General Public License version 3.0 -# requirements will be met: http://www.gnu.org/copyleft/gpl.html. -# -# If you do not wish to use this file under the terms of the GPL version 3.0 -# then you may purchase a commercial license. For more information contact -# info@riverbankcomputing.com. -# -# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - - -class Context: - """ Encapsulate a message context. """ - - def __init__(self, name): - """ Initialise the context. """ - - self.name = name - self.messages = [] - - -class EmbeddedComments: - """ Encapsulate information for a translator embedded in comments. """ - - def __init__(self): - """ Initialise the object. """ - - self.message_id = '' - self.extra_comments = [] - self.extras = [] - - -class Message: - """ Encapsulate a message. """ - - def __init__(self, filename, line_nr, source, comment, numerus): - """ Initialise the message. """ - - self.filename = filename - self.line_nr = line_nr - self.source = source - self.comment = comment - self.numerus = numerus - self.embedded_comments = EmbeddedComments() diff --git a/i18n/pylupdate6/user.py b/i18n/pylupdate6/user.py deleted file mode 100644 index 2ff01632..00000000 --- a/i18n/pylupdate6/user.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (c) 2021 Riverbank Computing Limited -# -# This file is part of PyQt6. -# -# This file may be used under the terms of the GNU General Public License -# version 3.0 as published by the Free Software Foundation and appearing in -# the file LICENSE included in the packaging of this file. Please review the -# following information to ensure the GNU General Public License version 3.0 -# requirements will be met: http://www.gnu.org/copyleft/gpl.html. -# -# If you do not wish to use this file under the terms of the GPL version 3.0 -# then you may purchase a commercial license. For more information contact -# info@riverbankcomputing.com. -# -# This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE -# WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - - -class UserException(Exception): - """ Encapsulate an exception ultimate caused by the user. """ - - pass - - -class User: - """ A mixin that provides methods for communicating with the user. """ - - def __init__(self, verbose, **kwargs): - """ Initialise the object. """ - - super().__init__(**kwargs) - - self._verbose = verbose - - @staticmethod - def pretty(text): - """ Returns a pretty-fied version of some text suitable for displaying - to the user. - """ - - return text.replace('\n', '\\n') - - def progress(self, message): - """ Display a progress message. """ - - if self._verbose: - print(message) diff --git a/setup.py b/setup.py index 2734af6f..8e6c02b8 100755 --- a/setup.py +++ b/setup.py @@ -331,6 +331,12 @@ def buildQtI18nTS(sysArgs): print("Building Qt Translation Files") print("=============================") + try: + from PyQt6.lupdate import lupdate + except ImportError: + print("ERROR: This command requires lupdate from PyQt6") + sys.exit(1) + print("") print("Scanning Source Tree:") print("") @@ -386,10 +392,7 @@ def buildQtI18nTS(sysArgs): print("Updating Language Files:") print("") - # Using the pylupdate tool from PyQt6 instead as it supports TS file - # format 2.1. This can perhaps be changed back to the installed tool - # at a later time. - from i18n.pylupdate6 import lupdate + # Using the pylupdate tool from PyQt6 as it supports TS file format 2.1. lupdate(srcList, tsList, no_obsolete=True, no_summary=False) print("") diff --git a/setup/debian/copyright b/setup/debian/copyright index f5986bc1..a60ea0d4 100644 --- a/setup/debian/copyright +++ b/setup/debian/copyright @@ -19,22 +19,6 @@ License: GPL-3.0-or-later You should have received a copy of the GNU General Public License along with this program. If not, see . -Files: i18n/pylupdate6/*.py -Copyright: 2021, Riverbank Computing Limited -License: GPL-3.0 - This file may be used under the terms of the GNU General Public License - version 3.0 as published by the Free Software Foundation and appearing in - the file LICENSE included in the packaging of this file. Please review the - following information to ensure the GNU General Public License version 3.0 - requirements will be met: http://www.gnu.org/copyleft/gpl.html. - . - If you do not wish to use this file under the terms of the GPL version 3.0 - then you may purchase a commercial license. For more information contact - info@riverbankcomputing.com. - . - This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE - WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. - Files: novelwriter/assets/icons/typicons_*.svg Copyright: 2019, Stephen Hutchings License: CC-BY-SA-4.0