diff --git a/advanced.py b/advanced.py index 8248dde..d2ffb8f 100644 --- a/advanced.py +++ b/advanced.py @@ -467,7 +467,8 @@ def layout_filter(self): categories = QComboBox() categories.addItem(_('All'), 'all') - categories.addItem(_('Non-aligned'), 'non_aligned') + if self.merge_enabled: + categories.addItem(_('Non-aligned'), 'non_aligned') categories.addItem(_('Translated'), 'translated') categories.addItem(_('Untranslated'), 'untranslated') diff --git a/components/table.py b/components/table.py index e453cca..d4341db 100644 --- a/components/table.py +++ b/components/table.py @@ -84,7 +84,7 @@ def track_row_data(self, row): paragraph = self.paragraph(row) if paragraph.translation: before_aligned = paragraph.aligned - self.check_row_alignment(paragraph) + self.parent.merge_enabled and self.check_row_alignment(paragraph) # If the alignment of before and after is the same, do nothing. if before_aligned and not paragraph.aligned: self.non_aligned_count += 1 diff --git a/lib/config.py b/lib/config.py index f3b5002..a3babdb 100644 --- a/lib/config.py +++ b/lib/config.py @@ -28,6 +28,7 @@ 'filter_scope': 'text', 'filter_rules': [], 'element_rules': [], + 'reserve_rules': [], 'custom_engines': {}, 'glossary_enabled': False, 'glossary_path': None, diff --git a/lib/element.py b/lib/element.py index 546407b..0a52778 100644 --- a/lib/element.py +++ b/lib/element.py @@ -5,7 +5,8 @@ from lxml import etree from calibre import prepare_string_for_xml as xml_escape -from .utils import ns, css, uid, trim, sorted_mixed_keys, open_file +from .utils import ( + ns, uid, trim, sorted_mixed_keys, open_file, css_to_xpath, create_xpath) from .config import get_config @@ -36,6 +37,9 @@ def __init__(self, element, page_id=None): self.original_color = None self.translation_color = None + self.remove_pattern = None + self.reserve_pattern = None + def _element_copy(self): return copy.deepcopy(self.element) @@ -60,6 +64,12 @@ def set_original_color(self, color): def set_translation_color(self, color): self.translation_color = color + def set_remove_pattern(self, pattern): + self.remove_pattern = pattern + + def set_reserve_pattern(self, pattern): + self.reserve_pattern = pattern + def get_name(self): return None @@ -152,7 +162,7 @@ def add_translation(self, translation=None): self.element.content = '%s %s' % ( translation, self.element.content) else: - self.element.content = '%s %s' %( + self.element.content = '%s %s' % ( self.element.content, translation) @@ -175,11 +185,6 @@ def add_translation(self, translation=None): class PageElement(Element): - def _get_descendents(self, element, tags): - tags = (tags,) if isinstance(tags, str) else tags - xpath = './/*[%s]' % ' or '.join(['self::x:%s' % tag for tag in tags]) - return element.xpath(xpath, namespaces=ns) - def get_name(self): return get_name(self.element) @@ -206,18 +211,16 @@ def _safe_remove(self, element, replacement=''): def get_content(self): element_copy = self._element_copy() - for noise in self._get_descendents(element_copy, ('rt', 'rp')): - self._safe_remove(noise) - # Reserve the
element instead of using a line break to prevent - # conflicts with the mechanism of merge translation. - target_elements = ( - 'img', 'code', 'br', 'hr', 'sub', 'sup', 'kbd', 'abbr', 'wbr', 'var', - 'canvas', 'svg', 'script', 'style') - self.reserve_elements = self._get_descendents( - element_copy, target_elements) + if self.remove_pattern is not None: + for noise in element_copy.xpath( + self.remove_pattern, namespaces=ns): + self._safe_remove(noise) + if self.reserve_pattern is not None: + self.reserve_elements = element_copy.xpath( + self.reserve_pattern, namespaces=ns) for eid, reserve in enumerate(self.reserve_elements): replacement = self.placeholder[0].format(format(eid, '05')) - if get_name(reserve) in ['sub', 'sup']: + if get_name(reserve) in ('sub', 'sup'): parent = reserve.getparent() if parent is not None and get_name(parent) == 'a' and \ parent.text is None and reserve.tail is None and \ @@ -423,6 +426,11 @@ def _create_table(self, translation=None): class Extraction: + priority_elements = ( + 'p', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote') + default_filter_rules = ( + r'^[-\d\s\.\'\\"‘’“”,=~!@#$%^&º*|≈<>?/`—…+:–_(){}[\]]+$',) + def __init__( self, pages, rule_mode, filter_scope, filter_rules, element_rules): self.pages = pages @@ -438,9 +446,7 @@ def __init__( self.load_element_patterns() def load_filter_patterns(self): - default_rules = [ - r'^[-\d\s\.\'\\"‘’“”,=~!@#$%^&º*|≈<>?/`—…+:–_(){}[\]]+$'] - patterns = [re.compile(rule) for rule in default_rules] + patterns = [re.compile(rule) for rule in self.default_filter_rules] for rule in self.filter_rules: if self.rule_mode == 'normal': rule = re.compile(re.escape(rule), re.I) @@ -452,13 +458,9 @@ def load_filter_patterns(self): self.filter_patterns = patterns def load_element_patterns(self): - rules = ['pre', 'code'] - rules.extend(self.element_rules) - patterns = [] - for selector in rules: - rule = css(selector) - rule and patterns.append(rule) - self.element_patterns = patterns + default_selectors = ['pre', 'code'] + self.element_patterns = css_to_xpath( + default_selectors + self.element_rules) def get_sorted_pages(self): pages = [] @@ -483,15 +485,20 @@ def need_ignore(self, element): return False def extract_elements(self, page_id, root, elements=[]): - priority_elements = [ - 'p', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote'] + """If the root matches the pattern, return an empty list; otherwise, + just break the recursion without doing anything. + """ + if self.need_ignore(root): + return [] for element in root.findall('./*'): + if self.need_ignore(element): + continue element_has_content = False if element.text is not None and trim(element.text) != '': element_has_content = True else: children = element.findall('./*') - if children and get_name(element) in priority_elements: + if children and get_name(element) in self.priority_elements: element_has_content = True else: for child in children: @@ -540,6 +547,9 @@ def __init__(self, placeholder, separator, position, merge_length=0): self.translation_color = None self.column_gap = None + self.remove_pattern = None + self.reserve_pattern = None + self.elements = {} self.originals = [] @@ -559,6 +569,18 @@ def set_column_gap(self, values): if isinstance(values, tuple) and len(values) == 2: self.column_gap = values + def load_remove_rules(self, rules=[]): + default_rules = ('rt', 'rp') + self.remove_pattern = create_xpath(default_rules + tuple(rules)) + + def load_reserve_rules(self, rules=[]): + # Reserve the
element instead of using a line break to prevent + # conflicts with the mechanism of merge translation. + default_rules = ( + 'img', 'code', 'br', 'hr', 'sub', 'sup', 'kbd', 'abbr', 'wbr', + 'var', 'canvas', 'svg', 'script', 'style') + self.reserve_pattern = create_xpath(default_rules + tuple(rules)) + def prepare_original(self, elements): count = 0 for oid, element in enumerate(elements): @@ -569,6 +591,8 @@ def prepare_original(self, elements): element.set_translation_color(self.translation_color) if self.column_gap is not None: element.set_column_gap(self.column_gap) + element.set_remove_pattern(self.remove_pattern) + element.set_reserve_pattern(self.reserve_pattern) raw = element.get_raw() content = element.get_content() md5 = uid('%s%s' % (oid, content)) @@ -618,6 +642,8 @@ def prepare_original(self, elements): element.set_translation_color(self.translation_color) if self.column_gap is not None: element.set_column_gap(self.column_gap) + element.set_remove_pattern(self.remove_pattern) + element.set_reserve_pattern(self.reserve_pattern) code = element.get_raw() content = element.get_content() content += self.separator @@ -725,7 +751,7 @@ def get_page_elements(pages): config = get_config() rule_mode = config.get('rule_mode') filter_scope = config.get('filter_scope') - filter_rules = config.get('filter_rules') + filter_rules = config.get('filter_rules', []) element_rules = config.get('element_rules', []) extraction = Extraction( pages, rule_mode, filter_scope, filter_rules, element_rules) @@ -747,4 +773,6 @@ def get_element_handler(placeholder, separator): handler.set_column_gap((gap_type, column_gap.get(gap_type))) handler.set_original_color(config.get('original_color')) handler.set_translation_color(config.get('translation_color')) + handler.load_remove_rules(config.get('element_rules', [])) + handler.load_reserve_rules(config.get('reserve_rules', [])) return handler diff --git a/lib/utils.py b/lib/utils.py index 9c971d0..d170f22 100644 --- a/lib/utils.py +++ b/lib/utils.py @@ -25,6 +25,19 @@ def css(seletor): return None +def css_to_xpath(selectors): + patterns = [] + for selector in selectors: + rule = css(selector) + rule and patterns.append(rule) + return patterns + + +def create_xpath(selectors): + selectors = (selectors,) if isinstance(selectors, str) else selectors + return './/*[%s]' % ' or '.join(css_to_xpath(selectors)) + + def uid(*args): md5 = hashlib.md5() for arg in args: diff --git a/setting.py b/setting.py index a00d996..7427289 100644 --- a/setting.py +++ b/setting.py @@ -1072,12 +1072,25 @@ def choose_filter_mode(btn_id): self.element_rules.setMinimumHeight(100) self.element_rules.insertPlainText( '\n'.join(self.config.get('element_rules'))) - element_layout.addWidget(QLabel( _('CSS selectors to exclude elements. One rule per line:'))) element_layout.addWidget(self.element_rules) layout.addWidget(element_group) + # Reserve element + reserve_group = QGroupBox(_('Reserve Element')) + reserve_layout = QVBoxLayout(reserve_group) + self.reserve_rules = QPlainTextEdit() + self.reserve_rules.setPlaceholderText( + '%s %s' % (_('e.g.,'), 'span.footnote, a#footnote')) + self.reserve_rules.setMinimumHeight(100) + self.reserve_rules.insertPlainText( + '\n'.join(self.config.get('reserve_rules'))) + reserve_layout.addWidget(QLabel( + _('CSS selectors to reserve elements. One rule per line:'))) + reserve_layout.addWidget(self.reserve_rules) + layout.addWidget(reserve_group) + # Ebook Metadata metadata_group = QGroupBox(_('Ebook Metadata')) metadata_layout = QFormLayout(metadata_group) @@ -1280,6 +1293,18 @@ def update_content_config(self): self.config.delete('element_rules') element_rules and self.config.update(element_rules=element_rules) + # Reserve rules + rule_content = self.reserve_rules.toPlainText() + reserve_rules = [r for r in rule_content.split('\n') if r.strip()] + for rule in reserve_rules: + if css(rule) is None: + self.alert.pop( + _('{} is not a valid CSS seletor.') + .format(rule), 'warning') + return False + self.config.delete('reserve_rules') + reserve_rules and self.config.update(reserve_rules=reserve_rules) + # Ebook metadata ebook_metadata = self.config.get('ebook_metadata').copy() ebook_metadata.clear() diff --git a/tests/test_config.py b/tests/test_config.py index 50271d1..11bc8be 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -33,6 +33,7 @@ def test_default(self): 'filter_scope': 'text', 'filter_rules': [], 'element_rules': [], + 'reserve_rules': [], 'custom_engines': {}, 'glossary_enabled': False, 'glossary_path': None, diff --git a/tests/test_element.py b/tests/test_element.py index 9daccbe..f1d8ed8 100644 --- a/tests/test_element.py +++ b/tests/test_element.py @@ -1,3 +1,4 @@ +import re import unittest from unittest.mock import patch, Mock @@ -5,7 +6,7 @@ from calibre.ebooks.oeb.base import TOC, Metadata -from ..lib.utils import ns +from ..lib.utils import ns, create_xpath from ..lib.cache import Paragraph from ..lib.element import ( get_string, get_name, Extraction, ElementHandler, ElementHandlerMerge, @@ -20,7 +21,7 @@ class TestFunction(unittest.TestCase): def test_get_string(self): markup = '
' \ '

abc

def
' - element = etree.XML(markup).find('x:p', namespaces=ns) + element = etree.XML(markup).find('.//x:p', namespaces=ns) self.assertEqual( '

abc

', get_string(element, False)) @@ -115,6 +116,8 @@ def test_create_element(self): self.assertIsNone(self.element.translation_lang) self.assertIsNone(self.element.original_color) self.assertIsNone(self.element.translation_color) + self.assertIsNone(self.element.remove_pattern) + self.assertIsNone(self.element.reserve_pattern) def test_set_ignored(self): self.element.set_ignored(True) @@ -144,6 +147,14 @@ def test_set_translation_color(self): self.element.set_translation_color('green') self.assertEqual('green', self.element.translation_color) + def test_set_remove_pattern(self): + self.element.set_remove_pattern('.//*[self::x:sup]') + self.assertEqual('.//*[self::x:sup]', self.element.remove_pattern) + + def test_set_reserve_pattern(self): + self.element.set_reserve_pattern('.//*[self::x:sup]') + self.assertEqual('.//*[self::x:sup]', self.element.reserve_pattern) + def test_get_name(self): self.assertIsNone(self.element.get_name()) @@ -363,6 +374,9 @@ def setUp(self): """) self.paragraph = self.xhtml.find('.//x:p', namespaces=ns) self.element = PageElement(self.paragraph, 'p1') + self.element.remove_pattern = create_xpath(('rt',)) + self.element.reserve_pattern = create_xpath( + ('img', 'sup', 'br', 'code')) self.element.placeholder = Base.placeholder self.element.position = 'below' @@ -407,15 +421,16 @@ def test_get_content_with_sup_sub(self):

""") - self.element = PageElement(xhtml.find('.//x:p', namespaces=ns), 'p1') - self.element.placeholder = Base.placeholder + element = PageElement(xhtml.find('.//x:p', namespaces=ns), 'p1') + element.reserve_pattern = create_xpath(('sup',)) + element.placeholder = Base.placeholder content = ( 'a{{id_00000}} b{{id_00001}} x cx {{id_00002}} d{{id_00003}} x') - self.assertEqual(content, self.element.get_content()) - self.assertEqual('a', get_name(self.element.reserve_elements[0])) - self.assertEqual('sup', get_name(self.element.reserve_elements[1])) - self.assertEqual('sup', get_name(self.element.reserve_elements[2])) - self.assertEqual('sup', get_name(self.element.reserve_elements[3])) + self.assertEqual(content, element.get_content()) + self.assertEqual('a', get_name(element.reserve_elements[0])) + self.assertEqual('sup', get_name(element.reserve_elements[1])) + self.assertEqual('sup', get_name(element.reserve_elements[2])) + self.assertEqual('sup', get_name(element.reserve_elements[3])) def test_get_attributes(self): self.assertEqual('{"class": "abc"}', self.element.get_attributes()) @@ -634,6 +649,7 @@ def test_add_translation_line_break_below(self): """) element = PageElement(xhtml.find('.//x:p', namespaces=ns), 'p1') + element.reserve_pattern = create_xpath(('sup', 'img', 'br')) element.placeholder = Base.placeholder element.position = 'below' element.get_content() @@ -665,6 +681,7 @@ def test_add_translation_line_break_above(self): """) element = PageElement(xhtml.find('.//x:p', namespaces=ns), 'p1') + element.reserve_pattern = create_xpath(('sup', 'img', 'br')) element.placeholder = Base.placeholder element.position = 'above' element.get_content() @@ -725,6 +742,26 @@ def setUp(self): self.extraction = Extraction( [self.page_3, self.page_2, self.page_1], 'normal', 'text', [], []) + def test_create_extraction(self): + self.assertEqual( + ('p', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote'), + Extraction.priority_elements) + self.assertEqual( + (r'^[-\d\s\.\'\\"‘’“”,=~!@#$%^&º*|≈<>?/`—…+:–_(){}[\]]+$',), + Extraction.default_filter_rules) + self.assertIsInstance(self.extraction, Extraction) + self.assertEqual( + [self.page_3, self.page_2, self.page_1], self.extraction.pages) + self.assertEqual('normal', self.extraction.rule_mode) + self.assertEqual('text', self.extraction.filter_scope) + self.assertEqual([], self.extraction.filter_rules) + self.assertEqual([], self.extraction.element_rules) + self.assertEqual( + [re.compile(Extraction.default_filter_rules[0])], + self.extraction.filter_patterns) + self.assertEqual( + ['self::x:pre', 'self::x:code'], self.extraction.element_patterns) + def test_get_sorted_pages(self): self.assertEqual( [self.page_1, self.page_2], self.extraction.get_sorted_pages()) @@ -804,27 +841,64 @@ def test_extract_elements(self): """) - root = xhtml.find('x:body', namespaces=ns) + root = xhtml.find('.//x:body', namespaces=ns) elements = self.extraction.extract_elements('p1', root, []) - self.assertEqual(9, len(elements)) + self.assertEqual(8, len(elements)) self.assertEqual('h2', elements[0].get_name()) self.assertFalse(elements[0].ignored) - self.assertEqual('pre', elements[-2].get_name()) - self.assertTrue(elements[-2].ignored) self.assertEqual('p', elements[-1].get_name()) self.assertFalse(elements[-1].ignored) + def test_extract_elements_root_wihout_elements(self): xhtml = etree.XML(b""" Document 123456789 """) - root = xhtml.find('x:body', namespaces=ns) + root = xhtml.find('.//x:body', namespaces=ns) + elements = self.extraction.extract_elements('test', root, []) self.assertEqual(1, len(elements)) self.assertEqual('123456789', elements[0].element.text) + def test_extract_elements_ignore_root(self): + xhtml = etree.XML(b""" + + +Document +

a

+""") + root = xhtml.find('.//x:body', namespaces=ns) + self.extraction.element_rules = ['.test'] + self.extraction.load_element_patterns() + + self.assertEqual( + [], self.extraction.extract_elements('test', root, [])) + + def test_extract_elements_ignore_certain_elements(self): + xhtml = etree.XML(b""" + + +Document + +

a

+

b

c

+

d

+

e

+ +""") + root = xhtml.find('.//x:body', namespaces=ns) + self.extraction.element_rules = ['.test', '#test'] + self.extraction.load_element_patterns() + + elements = self.extraction.extract_elements('test', root, []) + self.assertEqual(2, len(elements)) + self.assertEqual( + xhtml.find('.//x:div[1]/x:p', namespaces=ns), elements[0].element) + self.assertEqual( + xhtml.find('.//x:div[3]/x:p', namespaces=ns), elements[1].element) + def test_filter_content(self): def elements(markups): return [ @@ -935,6 +1009,8 @@ def test_create_element_handler_merge(self): self.assertIsNone(self.handler.original_color) self.assertIsNone(self.handler.translation_color) self.assertIsNone(self.handler.column_gap) + self.assertIsNone(self.handler.remove_pattern) + self.assertIsNone(self.handler.reserve_pattern) self.assertEqual({}, self.handler.elements) self.assertEqual([], self.handler.originals) @@ -963,8 +1039,24 @@ def test_set_column_gap(self): self.handler.set_column_gap(('percentage', 2)) self.assertEqual(('percentage', 2), self.handler.column_gap) + def test_load_remove_rules(self): + self.handler.load_remove_rules() + self.assertEqual( + './/*[self::x:rt or self::x:rp]', self.handler.remove_pattern) + + def test_load_reserve_rules(self): + self.handler.load_reserve_rules() + self.assertRegex( + self.handler.reserve_pattern, r'^\.//\*\[self::x:img.*style\]$') + @patch('calibre_plugins.ebook_translator.lib.element.uid') def test_prepare_original(self, mock_uid): + self.handler.translation_lang = 'en' + self.handler.original_color = 'red' + self.handler.translation_color = 'green' + self.handler.column_gap = ('percentage', 20) + self.handler.load_remove_rules() + self.handler.load_reserve_rules() mock_uid.side_effect = ['m1', 'm2', 'm3', 'm4', 'm5'] self.assertEqual([ (0, 'm1', '

a

', 'a', False, '{"id": "a"}', 'p1'), @@ -975,6 +1067,19 @@ def test_prepare_original(self, mock_uid): '{"id": "c", "class": "c"}', 'p1'), (4, 'm5', '

', '', True, None, 'p1')], self.handler.prepare_original(self.elements)) + for element in self.elements: + with self.subTest(element=element): + self.assertEqual(Base.placeholder, element.placeholder) + self.assertEqual('below', element.position) + self.assertEqual('en', element.translation_lang) + self.assertEqual('red', element.original_color) + self.assertEqual('green', element.translation_color) + self.assertEqual(('percentage', 20), element.column_gap) + self.assertEqual( + './/*[self::x:rt or self::x:rp]', + self.handler.remove_pattern) + self.assertRegex( + element.reserve_pattern, r'^\.//\*\[self::x:img.*style\]$') def test_prepare_translation(self): pass @@ -1146,17 +1251,36 @@ def test_align_paragraph(self): self.handler.position = 'above' self.assertEqual( - [('a', 'A\n\nB'),('b', None), ('c', None)], + [('a', 'A\n\nB'), ('b', None), ('c', None)], self.handler.align_paragraph(paragraph)) @patch('calibre_plugins.ebook_translator.lib.element.uid') def test_prepare_original_merge_separator(self, mock_uid): mock_uid.return_value = 'm1' self.handler.separator = Base.separator + self.handler.translation_lang = 'en' + self.handler.original_color = 'red' + self.handler.translation_color = 'green' + self.handler.column_gap = ('percentage', 20) + self.handler.load_remove_rules() + self.handler.load_reserve_rules() self.assertEqual([( 0, 'm1', '

a

\n\n

b

\n\n

c

\n\n', 'a\n\nb\n\nc\n\n', False)], self.handler.prepare_original(self.elements)) + for element in [e for e in self.elements if not e.ignored]: + with self.subTest(element=element): + self.assertEqual(Base.placeholder, element.placeholder) + self.assertEqual('below', element.position) + self.assertEqual('en', element.translation_lang) + self.assertEqual('red', element.original_color) + self.assertEqual('green', element.translation_color) + self.assertEqual(('percentage', 20), element.column_gap) + self.assertEqual( + './/*[self::x:rt or self::x:rp]', + self.handler.remove_pattern) + self.assertRegex( + element.reserve_pattern, r'^\.//\*\[self::x:img.*style\]$') @patch('calibre_plugins.ebook_translator.lib.element.uid') def test_prepare_original_merge_separator_multiple(self, mock_uid): diff --git a/translations/es.po b/translations/es.po index 2d6c325..81bac4d 100644 --- a/translations/es.po +++ b/translations/es.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Ebook Translator Calibre Plugin\n" "Report-Msgid-Bugs-To: bookfere@gmail.com\n" -"POT-Creation-Date: 2024-04-08 00:18+0800\n" +"POT-Creation-Date: 2024-04-08 23:03+0800\n" "PO-Revision-Date: 2023-04-17 14:17+0800\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -709,6 +709,12 @@ msgstr "" msgid "CSS selectors to exclude elements. One rule per line:" msgstr "" +msgid "Reserve Element" +msgstr "" + +msgid "CSS selectors to reserve elements. One rule per line:" +msgstr "" + msgid "Ebook Metadata" msgstr "" diff --git a/translations/fr.po b/translations/fr.po index 0849d3b..226ce62 100644 --- a/translations/fr.po +++ b/translations/fr.po @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: Ebook Translator Calibre Plugin\n" "Report-Msgid-Bugs-To: bookfere@gmail.com\n" -"POT-Creation-Date: 2024-04-08 00:18+0800\n" +"POT-Creation-Date: 2024-04-08 23:03+0800\n" "PO-Revision-Date: 2023-10-01 15:35-0400\n" "Last-Translator: PoP\n" @@ -720,6 +720,12 @@ msgstr "Ignorer l'élément" msgid "CSS selectors to exclude elements. One rule per line:" msgstr "Sélecteurs CSS pour exclure des éléments. Une règle par ligne:" +msgid "Reserve Element" +msgstr "" + +msgid "CSS selectors to reserve elements. One rule per line:" +msgstr "" + msgid "Ebook Metadata" msgstr "Metadata du ebook" diff --git a/translations/message.pot b/translations/message.pot index baf9fa9..d426b21 100644 --- a/translations/message.pot +++ b/translations/message.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Ebook Translator Calibre Plugin\n" "Report-Msgid-Bugs-To: bookfere@gmail.com\n" -"POT-Creation-Date: 2024-04-08 00:18+0800\n" +"POT-Creation-Date: 2024-04-08 23:03+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -51,7 +51,7 @@ msgstr "" msgid "Input Format" msgstr "" -#: advanced.py:236 advanced.py:683 batch.py:58 setting.py:369 +#: advanced.py:236 advanced.py:684 batch.py:58 setting.py:369 msgid "Target Language" msgstr "" @@ -91,127 +91,127 @@ msgstr "" msgid "All" msgstr "" -#: advanced.py:470 +#: advanced.py:471 msgid "Non-aligned" msgstr "" -#: advanced.py:471 advanced.py:773 components/table.py:94 +#: advanced.py:472 advanced.py:774 components/table.py:94 msgid "Translated" msgstr "" -#: advanced.py:472 components/table.py:83 +#: advanced.py:473 components/table.py:83 msgid "Untranslated" msgstr "" -#: advanced.py:475 +#: advanced.py:476 msgid "keyword for filtering" msgstr "" -#: advanced.py:554 +#: advanced.py:555 msgid "Total items: {}" msgstr "" -#: advanced.py:555 lib/translation.py:233 +#: advanced.py:556 lib/translation.py:233 msgid "Character count: {}" msgstr "" -#: advanced.py:564 +#: advanced.py:565 msgid "Non-aligned items: {}" msgstr "" -#: advanced.py:598 cache.py:92 cache.py:182 components/engine.py:202 +#: advanced.py:599 cache.py:92 cache.py:182 components/engine.py:202 #: components/table.py:139 msgid "Delete" msgstr "" -#: advanced.py:599 +#: advanced.py:600 msgid "Translate All" msgstr "" -#: advanced.py:600 +#: advanced.py:601 msgid "Translate Selected" msgstr "" -#: advanced.py:625 advanced.py:636 +#: advanced.py:626 advanced.py:637 msgid "Stop" msgstr "" -#: advanced.py:631 +#: advanced.py:632 msgid "Stopping..." msgstr "" -#: advanced.py:661 +#: advanced.py:662 msgid "Cache Status" msgstr "" -#: advanced.py:664 +#: advanced.py:665 msgid "Disabled" msgstr "" -#: advanced.py:664 +#: advanced.py:665 msgid "Enabled" msgstr "" -#: advanced.py:671 setting.py:328 +#: advanced.py:672 setting.py:328 msgid "Translation Engine" msgstr "" -#: advanced.py:677 batch.py:58 setting.py:368 +#: advanced.py:678 batch.py:58 setting.py:368 msgid "Source Language" msgstr "" -#: advanced.py:689 +#: advanced.py:690 msgid "Custom Ebook Title" msgstr "" -#: advanced.py:694 +#: advanced.py:695 msgid "By default, title metadata will be translated." msgstr "" -#: advanced.py:723 +#: advanced.py:724 msgid "Output Ebook" msgstr "" -#: advanced.py:725 +#: advanced.py:726 msgid "Output" msgstr "" -#: advanced.py:774 +#: advanced.py:775 msgid "The ebook has not been translated yet." msgstr "" -#: advanced.py:778 +#: advanced.py:779 msgid "" "The number of lines in some translation units differs between the original " "text and the translated text. Are you sure you want to output without " "checking alignment?" msgstr "" -#: advanced.py:813 +#: advanced.py:814 msgid "No translation yet" msgstr "" -#: advanced.py:866 components/engine.py:208 setting.py:98 +#: advanced.py:867 components/engine.py:208 setting.py:98 msgid "Save" msgstr "" -#: advanced.py:922 +#: advanced.py:923 msgid "Your changes have been saved." msgstr "" -#: advanced.py:935 +#: advanced.py:936 msgid "Translation log" msgstr "" -#: advanced.py:946 +#: advanced.py:947 msgid "Error log" msgstr "" -#: advanced.py:966 +#: advanced.py:967 msgid "Are you sure you want to translate all {:n} paragraphs?" msgstr "" -#: advanced.py:992 +#: advanced.py:993 msgid "Are you sure you want to stop the translation progress?" msgstr "" @@ -235,7 +235,7 @@ msgstr "" msgid "Unknown" msgstr "" -#: batch.py:160 cache.py:130 setting.py:1136 +#: batch.py:160 cache.py:130 setting.py:1149 msgid "The specified path does not exist." msgstr "" @@ -374,7 +374,7 @@ msgstr "" msgid "Feedback" msgstr "" -#: components/lang.py:34 engines/base.py:70 setting.py:1198 +#: components/lang.py:34 engines/base.py:70 setting.py:1211 msgid "Auto detect" msgstr "" @@ -871,7 +871,7 @@ msgstr "" msgid "Original Text Color" msgstr "" -#: setting.py:894 setting.py:912 setting.py:1071 +#: setting.py:894 setting.py:912 setting.py:1071 setting.py:1085 msgid "e.g.," msgstr "" @@ -935,67 +935,75 @@ msgstr "" msgid "Ignore Element" msgstr "" -#: setting.py:1077 +#: setting.py:1076 msgid "CSS selectors to exclude elements. One rule per line:" msgstr "" -#: setting.py:1082 +#: setting.py:1081 +msgid "Reserve Element" +msgstr "" + +#: setting.py:1090 +msgid "CSS selectors to reserve elements. One rule per line:" +msgstr "" + +#: setting.py:1095 msgid "Ebook Metadata" msgstr "" -#: setting.py:1086 +#: setting.py:1099 msgid "Append target language to title metadata" msgstr "" -#: setting.py:1088 +#: setting.py:1101 msgid "Set target language code to language metadata" msgstr "" -#: setting.py:1091 +#: setting.py:1104 msgid "Subjects of ebook (one subject per line)" msgstr "" -#: setting.py:1092 +#: setting.py:1105 msgid "Language Mark" msgstr "" -#: setting.py:1093 +#: setting.py:1106 msgid "Language Code" msgstr "" -#: setting.py:1094 +#: setting.py:1107 msgid "Append Subjects" msgstr "" -#: setting.py:1115 setting.py:1150 +#: setting.py:1128 setting.py:1163 msgid "Proxy host or port is incorrect." msgstr "" -#: setting.py:1117 +#: setting.py:1130 msgid "The proxy is available." msgstr "" -#: setting.py:1118 +#: setting.py:1131 msgid "The proxy is not available." msgstr "" -#: setting.py:1208 +#: setting.py:1221 msgid "the prompt must include {}." msgstr "" -#: setting.py:1237 setting.py:1244 +#: setting.py:1250 setting.py:1257 msgid "Invalid color value." msgstr "" -#: setting.py:1253 +#: setting.py:1266 msgid "The specified glossary file does not exist." msgstr "" -#: setting.py:1265 +#: setting.py:1278 msgid "{} is not a valid regular expression." msgstr "" -#: setting.py:1277 +#: setting.py:1290 setting.py:1302 msgid "{} is not a valid CSS seletor." msgstr "" diff --git a/translations/pt.po b/translations/pt.po index 8c7ef31..bd0647e 100644 --- a/translations/pt.po +++ b/translations/pt.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Ebook Translator Calibre Plugin\n" "Report-Msgid-Bugs-To: bookfere@gmail.com\n" -"POT-Creation-Date: 2024-04-08 00:18+0800\n" +"POT-Creation-Date: 2024-04-08 23:03+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: none\n" @@ -723,6 +723,12 @@ msgstr "Ignorar Elemento" msgid "CSS selectors to exclude elements. One rule per line:" msgstr "Seletores CSS para excluir elementos. Uma regra por linha:" +msgid "Reserve Element" +msgstr "" + +msgid "CSS selectors to reserve elements. One rule per line:" +msgstr "" + msgid "Ebook Metadata" msgstr "Metadados dos ebooks" diff --git a/translations/tr.po b/translations/tr.po index bdcf188..5a984a1 100644 --- a/translations/tr.po +++ b/translations/tr.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Ebook Translator Calibre Plugin\n" "Report-Msgid-Bugs-To: bookfere@gmail.com\n" -"POT-Creation-Date: 2024-04-08 00:18+0800\n" +"POT-Creation-Date: 2024-04-08 23:03+0800\n" "PO-Revision-Date: 2024-03-23 15:12+0300\n" "Last-Translator: DogancanYr \n" "Language-Team: Turkish \n" @@ -722,6 +722,12 @@ msgstr "Öğeyi Yoksay" msgid "CSS selectors to exclude elements. One rule per line:" msgstr "Öğeleri dışlamak için CSS seçicileri. " +msgid "Reserve Element" +msgstr "" + +msgid "CSS selectors to reserve elements. One rule per line:" +msgstr "" + msgid "Ebook Metadata" msgstr "E-kitap Meta Verisi" diff --git a/translations/zh_CN.mo b/translations/zh_CN.mo index a5f578a..190abe4 100644 Binary files a/translations/zh_CN.mo and b/translations/zh_CN.mo differ diff --git a/translations/zh_CN.po b/translations/zh_CN.po index c365b62..fa361a5 100644 --- a/translations/zh_CN.po +++ b/translations/zh_CN.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Ebook Translator Calibre Plugin\n" "Report-Msgid-Bugs-To: bookfere@gmail.com\n" -"POT-Creation-Date: 2024-04-08 00:18+0800\n" +"POT-Creation-Date: 2024-04-08 23:03+0800\n" "PO-Revision-Date: 2023-04-17 14:17+0800\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -709,6 +709,12 @@ msgstr "忽略元素" msgid "CSS selectors to exclude elements. One rule per line:" msgstr "用来排除元素的 CSS 选择器。一行一条规则:" +msgid "Reserve Element" +msgstr "保留元素" + +msgid "CSS selectors to reserve elements. One rule per line:" +msgstr "用来保留元素的 CSS 选择器。一行一条规则:" + msgid "Ebook Metadata" msgstr "电子书元数据" diff --git a/translations/zh_TW.po b/translations/zh_TW.po index 372ff8d..7ad5f3b 100644 --- a/translations/zh_TW.po +++ b/translations/zh_TW.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Ebook Translator Calibre Plugin\n" "Report-Msgid-Bugs-To: bookfere@gmail.com\n" -"POT-Creation-Date: 2024-04-08 00:18+0800\n" +"POT-Creation-Date: 2024-04-08 23:03+0800\n" "PO-Revision-Date: 2023-04-25 15:36+0800\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -705,6 +705,12 @@ msgstr "" msgid "CSS selectors to exclude elements. One rule per line:" msgstr "" +msgid "Reserve Element" +msgstr "" + +msgid "CSS selectors to reserve elements. One rule per line:" +msgstr "" + msgid "Ebook Metadata" msgstr ""