From c89e7352a1f3d15e2ea0d948ad98672352fed44d Mon Sep 17 00:00:00 2001 From: bookfere Date: Mon, 11 Mar 2024 22:06:22 +0800 Subject: [PATCH] feat: Added the ability to translate filename and metadata. resolved #216 --- advanced.py | 36 +++--- lib/conversion.py | 7 +- lib/element.py | 53 +++++++- tests/test_cache.py | 1 - tests/test_element.py | 63 +++++++++- translations/es.po | 20 ++- translations/fr.po | 20 ++- translations/message.pot | 258 ++++++++++++++++++++------------------- translations/pt.po | 20 ++- translations/zh_CN.mo | Bin 16476 -> 16756 bytes translations/zh_CN.po | 34 ++++-- translations/zh_TW.po | 20 ++- 12 files changed, 349 insertions(+), 183 deletions(-) diff --git a/advanced.py b/advanced.py index 0d0b320..11192bd 100644 --- a/advanced.py +++ b/advanced.py @@ -235,9 +235,7 @@ def show_advanced(self): class AdvancedTranslation(QDialog): - raw_text = pyqtSignal(str) - original_text = pyqtSignal(str) - translation_text = pyqtSignal((), (str,)) + paragraph_sig = pyqtSignal(Paragraph) progress_bar = pyqtSignal() preparation_thread = QThread() @@ -504,8 +502,7 @@ def terminate_translation(): def terminate_finished(): stop_button.setDisabled(False) stop_button.setText(_('Stop')) - self.translation_text[str].emit( - self.table.current_paragraph().translation) + self.paragraph_sig.emit(self.table.current_paragraph()) self.trans_worker.finished.connect(terminate_finished) stack = QStackedWidget() @@ -565,11 +562,18 @@ def layout_control(self): ebook_title.setCursorPosition(0) output_format = OutputFormat() output_format.setFixedWidth(150) - save_layout.addWidget(QLabel(_('Title'))) + save_layout.addWidget(QLabel(_('Filename'))) save_layout.addWidget(ebook_title, 1) + save_layout.addWidget(QLabel(_('Format'))) save_layout.addWidget(output_format) save_layout.addWidget(save_ebook) + if self.config.get('to_library'): + ebook_title.setDisabled(True) + ebook_title.setToolTip(_( + "The ebook's filename is automatically managed by Calibre " + 'according to metadata since the output path is set to ' + 'Calibre Library.')) ebook_title.textChanged.connect(self.ebook.set_title) layout.addWidget(cache_group) @@ -662,10 +666,12 @@ def layout_review(self): translation_text.cursorPositionChanged.connect( translation_text.ensureCursorVisible) - self.raw_text.connect(raw_text.setPlainText) - self.original_text.connect(original_text.setPlainText) - self.translation_text.connect(translation_text.clear) - self.translation_text[str].connect(translation_text.setPlainText) + def refresh_translation(paragraph): + translation_text.clear() + raw_text.setPlainText(paragraph.raw) + original_text.setPlainText(paragraph.original) + translation_text.setPlainText(paragraph.translation) + self.paragraph_sig.connect(refresh_translation) self.trans_worker.start.connect( lambda: translation_text.setReadOnly(False)) self.trans_worker.finished.connect( @@ -715,25 +721,21 @@ def change_selected_item(): paragraph = self.table.current_paragraph() if paragraph is None: return - self.raw_text.emit(paragraph.raw) - self.original_text.emit(paragraph.original.strip()) - self.translation_text[str].emit(paragraph.translation) + self.paragraph_sig.emit(paragraph) self.table.itemSelectionChanged.connect(change_selected_item) self.table.setCurrentItem(self.table.item(0, 0)) change_selected_item() def translation_callback(paragraph): self.table.row.emit(paragraph.row) - self.raw_text.emit(paragraph.raw) - self.original_text.emit(paragraph.original) - self.translation_text[str].emit(paragraph.translation) + self.paragraph_sig.emit(paragraph) self.cache.update_paragraph(paragraph) self.progress_bar.emit() self.trans_worker.callback.connect(translation_callback) def streaming_translation(data): if data == '': - self.translation_text.emit() + self.paragraph_sig.emit(self.table.current_paragraph()) elif isinstance(data, Paragraph): self.table.setCurrentItem(self.table.item(data.row, 0)) else: diff --git a/lib/conversion.py b/lib/conversion.py index 2d1b510..135ee9c 100644 --- a/lib/conversion.py +++ b/lib/conversion.py @@ -15,8 +15,8 @@ from .utils import log, sep, uid, open_path from .cache import get_cache, TranslationCache from .element import ( - get_srt_elements, get_toc_elements, get_page_elements, get_element_handler, - Extraction) + Extraction, get_element_handler, get_srt_elements, get_toc_elements, + get_page_elements, get_metadata_elements) from .translation import get_translator, get_translation @@ -36,6 +36,7 @@ def extract_book(input_path): plumber = Plumber(input_path, output_path, log=log) def convert(self, oeb, output_path, input_plugin, opts, log): + elements.extend(get_metadata_elements(oeb.metadata)) elements.extend(get_toc_elements(oeb.toc.nodes, [])) elements.extend(get_page_elements(oeb.manifest.items)) plumber.output_plugin.convert = MethodType(convert, plumber.output_plugin) @@ -117,6 +118,8 @@ def convert(self, oeb, output_path, input_plugin, opts, log): log.info(debug_info) translation.set_progress(self.report_progress) + elements.extend(get_metadata_elements(oeb.metadata)) + # The number of elements may vary with format conversion. elements.extend(get_toc_elements(oeb.toc.nodes, [])) elements.extend(get_page_elements(oeb.manifest.items)) original_group = element_handler.prepare_original(elements) diff --git a/lib/element.py b/lib/element.py index 60d981b..583844b 100644 --- a/lib/element.py +++ b/lib/element.py @@ -83,6 +83,31 @@ def add_translation( return self.element +class MetadataElement(Element): + def get_raw(self): + return self.element.content + + def get_text(self): + return self.element.content + + def get_content(self, placeholder): + return self.element.content + + def add_translation( + self, translation, placeholder, position, translation_lang=None, + original_color=None, translation_color=None): + if translation is not None: + if position == 'only': + self.element.content = translation + elif position in ['above', 'left']: + self.element.content = '%s %s' % ( + translation, self.element.content) + else: + self.element.content = '%s %s' %( + self.element.content, translation) + return self.element + + class TocElement(Element): def get_raw(self): return self.element.title @@ -104,9 +129,9 @@ def add_translation( class PageElement(Element): - def _get_descendents(self, tags): + def _get_descendents(self, element, tags): xpath = './/*[%s]' % ' or '.join(['self::x:%s' % tag for tag in tags]) - return self._element_copy().xpath(xpath, namespaces=ns) + return element.xpath(xpath, namespaces=ns) def get_name(self): return get_name(self.element) @@ -125,13 +150,15 @@ def delete(self): self.element.getparent().remove(self.element) def get_content(self, placeholder): - for noise in self._get_descendents(('rt', 'rp', 'sup', 'sub')): + element_copy = self._element_copy() + for noise in self._get_descendents( + element_copy, ('rt', 'rp', 'sup', 'sub')): parent = noise.getparent() parent.text = (parent.text or '') + (noise.tail or '') parent.remove(noise) self.reserve_elements = self._get_descendents( - ('img', 'code', 'br', 'hr', 'sub', 'sup', 'kbd')) + element_copy, ('img', 'code', 'br', 'hr', 'sub', 'sup', 'kbd')) count = 0 for reserve in self.reserve_elements: replacement = placeholder[0].format(format(count, '05')) @@ -146,7 +173,7 @@ def get_content(self, placeholder): parent.remove(reserve) count += 1 - return trim(''.join(self._element_copy().itertext())) + return trim(''.join(element_copy.itertext())) def _polish_translation(self, translation): translation = translation.replace('\n', '
') @@ -513,6 +540,22 @@ def get_srt_elements(path): return [SrtElement(section) for section in sections] +def get_metadata_elements(metadata): + elements = [] + names = ( + 'title', 'creator', 'publisher', 'rights', 'subject', 'contributor') + pattern = re.compile(r'[a-z]+') + for key in metadata.iterkeys(): + if key not in names: + continue + items = getattr(metadata, key) + for item in items: + if pattern.search(item.content) is None: + continue + elements.append(MetadataElement(item, 'content.opf')) + return elements + + def get_toc_elements(nodes, elements=[]): """Be aware that elements should not overlap with existing data.""" for node in nodes: diff --git a/tests/test_cache.py b/tests/test_cache.py index eff6cfe..1d374fe 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -14,7 +14,6 @@ def test_created_paragraph(self): self.assertFalse(self.paragraph.is_cache) self.assertIsNone(self.paragraph.error) self.assertTrue(self.paragraph.aligned) - self.assertIsNone(self.paragraph.background) def test_get_attributes(self): self.assertEqual({'class': 'test'}, self.paragraph.get_attributes()) diff --git a/tests/test_element.py b/tests/test_element.py index f61923c..e14e5b4 100644 --- a/tests/test_element.py +++ b/tests/test_element.py @@ -3,13 +3,14 @@ from lxml import etree -from calibre.ebooks.oeb.base import TOC +from calibre.ebooks.oeb.base import TOC, Metadata from ..lib.utils import ns from ..lib.cache import Paragraph from ..lib.element import ( - get_string, get_name, SrtElement, TocElement, PageElement, Extraction, - ElementHandler, ElementHandlerMerge, get_toc_elements) + get_string, get_name, Extraction, ElementHandler, ElementHandlerMerge, + SrtElement, TocElement, PageElement, MetadataElement, get_toc_elements, + get_metadata_elements) from ..engines import DeeplFreeTranslate from ..engines.base import Base @@ -45,6 +46,22 @@ def test_get_toc_elements(self): elements = get_toc_elements(toc, []) self.assertEqual(3, len(elements)) + def test_get_metadata_elements(self): + metadata = Mock(Metadata) + item_1 = Mock(Metadata.Item, content='a') + item_2 = Mock(Metadata.Item, content='b') + item_3 = Mock(Metadata.Item, content='0') + metadata.title = [item_1] + metadata.subject = [item_2, item_3] + metadata.language = [] + metadata.iterkeys.return_value = ['title', 'subject', 'language'] + + elements = get_metadata_elements(metadata) + + self.assertEqual(2, len(elements)) + self.assertIs(item_1, elements[0].element) + self.assertIs(item_2, elements[1].element) + class TestSrtElement(unittest.TestCase): def setUp(self): @@ -84,6 +101,46 @@ def test_add_translation_only(self): self.assertEqual('A', element[2]) +class TestMetadataElement(unittest.TestCase): + def setUp(self): + self.medata_item = Mock(Metadata.Item, content='a') + self.element = MetadataElement(self.medata_item) + + def test_get_raw(self): + self.assertEqual('a', self.element.get_raw()) + + def test_get_text(self): + self.assertEqual('a', self.element.get_text()) + + def test_get_content(self): + self.assertEqual('a', self.element.get_content(Base.placeholder)) + + def test_add_translation_none(self): + self.assertIs( + self.element.element, + self.element.add_translation(None, Base.placeholder, 'below')) + + def test_add_translation_below(self): + element = self.element.add_translation('A', Base.placeholder, 'below') + self.assertEqual('a A', element.content) + + def test_add_translation_right(self): + element = self.element.add_translation('A', Base.placeholder, 'right') + self.assertEqual('a A', element.content) + + def test_add_translation_above(self): + element = self.element.add_translation('A', Base.placeholder, 'above') + self.assertEqual('A a', element.content) + + def test_add_translation_left(self): + element = self.element.add_translation('A', Base.placeholder, 'left') + self.assertEqual('A a', element.content) + + def test_add_translation_only(self): + element = self.element.add_translation('A', Base.placeholder, 'only') + self.assertEqual('A', element.content) + + class TestTocElement(unittest.TestCase): def setUp(self): self.element = TocElement(TOC('a', 'a.html'), 'toc.ncx') diff --git a/translations/es.po b/translations/es.po index 84d7acc..ac55afa 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-03-10 12:31+0800\n" +"POT-Creation-Date: 2024-03-11 21:06+0800\n" "PO-Revision-Date: 2023-04-17 14:17+0800\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -109,8 +109,16 @@ msgstr "" msgid "Output" msgstr "" -msgid "Title" -msgstr "Título" +msgid "Filename" +msgstr "" + +msgid "Format" +msgstr "" + +msgid "" +"The ebook's filename is automatically managed by Calibre according to " +"metadata since the output path is set to Calibre Library." +msgstr "" msgid "Translated" msgstr "" @@ -148,6 +156,9 @@ msgstr "" msgid "Output Format" msgstr "Formato de salida" +msgid "Title" +msgstr "Título" + msgid "Translate" msgstr "Traducir" @@ -206,9 +217,6 @@ msgstr "" msgid "Merge Length" msgstr "" -msgid "Filename" -msgstr "" - msgid "Size (MB)" msgstr "" diff --git a/translations/fr.po b/translations/fr.po index 4abec3c..67c278e 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-03-10 12:31+0800\n" +"POT-Creation-Date: 2024-03-11 21:06+0800\n" "PO-Revision-Date: 2023-10-01 15:35-0400\n" "Last-Translator: PoP\n" @@ -109,8 +109,16 @@ msgstr "Produit ebook" msgid "Output" msgstr "Produit" -msgid "Title" -msgstr "Titre" +msgid "Filename" +msgstr "Nom du fichier" + +msgid "Format" +msgstr "" + +msgid "" +"The ebook's filename is automatically managed by Calibre according to " +"metadata since the output path is set to Calibre Library." +msgstr "" msgid "Translated" msgstr "Traduit" @@ -148,6 +156,9 @@ msgstr "Êtes-vous certain de vouloir arrêter la traduction?" msgid "Output Format" msgstr "Format de sortie" +msgid "Title" +msgstr "Titre" + msgid "Translate" msgstr "Traduire" @@ -210,9 +221,6 @@ msgstr "Langue" msgid "Merge Length" msgstr "Longueur de fusion" -msgid "Filename" -msgstr "Nom du fichier" - msgid "Size (MB)" msgstr "Taille (MB)" diff --git a/translations/message.pot b/translations/message.pot index 3427a54..748fa43 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-03-10 12:31+0800\n" +"POT-Creation-Date: 2024-03-11 21:06+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -43,11 +43,11 @@ msgstr "" msgid "Start" msgstr "" -#: advanced.py:199 batch.py:54 setting.py:177 +#: advanced.py:199 batch.py:54 setting.py:184 msgid "Input Format" msgstr "" -#: advanced.py:210 advanced.py:544 batch.py:55 setting.py:360 +#: advanced.py:210 advanced.py:544 batch.py:55 setting.py:367 msgid "Target Language" msgstr "" @@ -87,7 +87,7 @@ msgstr "" msgid "Character count: {}" msgstr "" -#: advanced.py:468 cache.py:92 components/engine.py:200 components/table.py:117 +#: advanced.py:468 cache.py:92 components/engine.py:200 components/table.py:135 msgid "Delete" msgstr "" @@ -107,11 +107,11 @@ msgstr "" msgid "Stopping..." msgstr "" -#: advanced.py:532 setting.py:319 +#: advanced.py:532 setting.py:326 msgid "Translation Engine" msgstr "" -#: advanced.py:538 batch.py:55 setting.py:359 +#: advanced.py:538 batch.py:55 setting.py:366 msgid "Source Language" msgstr "" @@ -135,66 +135,80 @@ msgstr "" msgid "Output" msgstr "" -#: advanced.py:568 batch.py:54 cache.py:190 -msgid "Title" +#: advanced.py:568 cache.py:191 +msgid "Filename" +msgstr "" + +#: advanced.py:570 +msgid "Format" +msgstr "" + +#: advanced.py:577 +msgid "" +"The ebook's filename is automatically managed by Calibre according to " +"metadata since the output path is set to Calibre Library." msgstr "" -#: advanced.py:616 components/table.py:74 +#: advanced.py:623 components/table.py:89 msgid "Translated" msgstr "" -#: advanced.py:617 +#: advanced.py:624 msgid "The ebook has not been translated yet." msgstr "" -#: advanced.py:621 +#: advanced.py:628 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:656 +#: advanced.py:663 msgid "No translation yet" msgstr "" -#: advanced.py:703 components/engine.py:206 setting.py:89 +#: advanced.py:710 components/engine.py:206 setting.py:96 msgid "Save" msgstr "" -#: advanced.py:763 +#: advanced.py:770 msgid "Your changes have been saved." msgstr "" -#: advanced.py:776 +#: advanced.py:783 msgid "Translation log" msgstr "" -#: advanced.py:787 +#: advanced.py:794 msgid "Error log" msgstr "" -#: advanced.py:807 +#: advanced.py:814 msgid "Are you sure you want to translate all {:n} paragraphs?" msgstr "" -#: advanced.py:833 +#: advanced.py:840 msgid "Are you sure you want to stop the translation progress?" msgstr "" -#: batch.py:54 setting.py:178 +#: batch.py:54 setting.py:185 msgid "Output Format" msgstr "" -#: batch.py:126 components/engine.py:137 components/table.py:115 +#: batch.py:54 cache.py:190 +msgid "Title" +msgstr "" + +#: batch.py:126 components/engine.py:137 components/table.py:133 msgid "Translate" msgstr "" -#: batch.py:137 lib/cache.py:128 +#: batch.py:137 lib/cache.py:126 msgid "Unknown" msgstr "" -#: batch.py:146 cache.py:130 setting.py:1017 +#: batch.py:146 cache.py:130 setting.py:1027 msgid "The specified path does not exist." msgstr "" @@ -202,8 +216,8 @@ msgstr "" msgid "Choose a path to store cache files." msgstr "" -#: cache.py:73 components/mode.py:41 components/mode.py:51 setting.py:149 -#: setting.py:790 setting.py:809 setting.py:857 +#: cache.py:73 components/mode.py:41 components/mode.py:51 setting.py:156 +#: setting.py:800 setting.py:819 setting.py:867 msgid "Choose" msgstr "" @@ -252,11 +266,11 @@ msgstr "" msgid "Total: {}" msgstr "" -#: cache.py:190 components/table.py:37 setting.py:54 +#: cache.py:190 components/table.py:40 setting.py:61 msgid "Engine" msgstr "" -#: cache.py:190 components/table.py:37 setting.py:978 +#: cache.py:190 components/table.py:40 setting.py:988 msgid "Language" msgstr "" @@ -264,10 +278,6 @@ msgstr "" msgid "Merge Length" msgstr "" -#: cache.py:191 -msgid "Filename" -msgstr "" - #: cache.py:191 msgid "Size (MB)" msgstr "" @@ -313,11 +323,11 @@ msgstr "" msgid "The engine name is already in use." msgstr "" -#: components/engine.py:285 setting.py:68 +#: components/engine.py:285 setting.py:75 msgid "The setting has been saved." msgstr "" -#: components/format.py:27 setting.py:187 +#: components/format.py:27 setting.py:194 msgid "Ebook Specific" msgstr "" @@ -329,15 +339,15 @@ msgstr "" msgid "Feedback" msgstr "" -#: components/lang.py:34 engines/base.py:70 setting.py:1078 +#: components/lang.py:34 engines/base.py:70 setting.py:1088 msgid "Auto detect" msgstr "" -#: components/mode.py:28 setting.py:116 +#: components/mode.py:28 setting.py:123 msgid "Choose a translation mode for clicking the icon button." msgstr "" -#: components/mode.py:33 setting.py:106 ui.py:54 ui.py:82 +#: components/mode.py:33 setting.py:113 ui.py:54 ui.py:82 msgid "Advanced Mode" msgstr "" @@ -347,7 +357,7 @@ msgid "" "for more control and customization." msgstr "" -#: components/mode.py:43 setting.py:107 ui.py:55 ui.py:109 +#: components/mode.py:43 setting.py:114 ui.py:55 ui.py:109 msgid "Batch Mode" msgstr "" @@ -357,33 +367,33 @@ msgid "" "the translation process and saving time." msgstr "" -#: components/table.py:37 setting.py:712 +#: components/table.py:40 setting.py:719 msgid "Original" msgstr "" -#: components/table.py:37 +#: components/table.py:40 msgid "Status" msgstr "" -#: components/table.py:69 +#: components/table.py:84 msgid "Untranslated" msgstr "" -#: components/table.py:95 +#: components/table.py:111 msgid "" "The number of lines differs between the original text and the translated " "text." msgstr "" -#: components/table.py:121 +#: components/table.py:139 msgid "Select the whole chapter" msgstr "" -#: components/table.py:127 +#: components/table.py:145 msgid "Select similar paragraphs: {}=\"{}\"" msgstr "" -#: components/table.py:174 +#: components/table.py:192 msgid "Retain at least one row." msgstr "" @@ -391,7 +401,7 @@ msgstr "" msgid "Baidu" msgstr "" -#: engines/base.py:20 setting.py:340 +#: engines/base.py:20 setting.py:347 msgid "API Keys" msgstr "" @@ -577,323 +587,323 @@ msgstr "" msgid "Translation failed." msgstr "" -#: setting.py:53 +#: setting.py:60 msgid "General" msgstr "" -#: setting.py:55 +#: setting.py:62 msgid "Content" msgstr "" -#: setting.py:104 +#: setting.py:111 msgid "Preferred Mode" msgstr "" -#: setting.py:138 +#: setting.py:145 msgid "Output Path" msgstr "" -#: setting.py:140 +#: setting.py:147 msgid "Library" msgstr "" -#: setting.py:141 +#: setting.py:148 msgid "Path" msgstr "" -#: setting.py:146 +#: setting.py:153 msgid "Choose a path to store translated book(s)" msgstr "" -#: setting.py:173 +#: setting.py:180 msgid "Preferred Format" msgstr "" -#: setting.py:197 +#: setting.py:204 msgid "(Beta)" msgstr "" -#: setting.py:197 +#: setting.py:204 msgid "Merge to Translate" msgstr "" -#: setting.py:199 setting.py:220 setting.py:264 setting.py:854 +#: setting.py:206 setting.py:227 setting.py:271 setting.py:864 msgid "Enable" msgstr "" -#: setting.py:205 +#: setting.py:212 msgid "The number of characters to translate at once." msgstr "" -#: setting.py:217 +#: setting.py:224 msgid "HTTP Proxy" msgstr "" -#: setting.py:232 +#: setting.py:239 msgid "Host" msgstr "" -#: setting.py:235 +#: setting.py:242 msgid "Port" msgstr "" -#: setting.py:246 setting.py:322 +#: setting.py:253 setting.py:329 msgid "Test" msgstr "" -#: setting.py:262 ui.py:57 +#: setting.py:269 ui.py:57 msgid "Cache" msgstr "" -#: setting.py:265 +#: setting.py:272 msgid "Manage" msgstr "" -#: setting.py:282 +#: setting.py:289 msgid "Job Log" msgstr "" -#: setting.py:283 +#: setting.py:290 msgid "Show translation" msgstr "" -#: setting.py:296 +#: setting.py:303 msgid "Search Paths" msgstr "" -#: setting.py:299 +#: setting.py:306 msgid "The plugin will search for external programs via these paths." msgstr "" -#: setting.py:323 setting.py:529 setting.py:533 setting.py:540 setting.py:545 +#: setting.py:330 setting.py:536 setting.py:540 setting.py:547 setting.py:552 msgid "Custom" msgstr "" -#: setting.py:330 +#: setting.py:337 msgid "Using Tip" msgstr "" -#: setting.py:344 +#: setting.py:351 msgid "Tip:" msgstr "" -#: setting.py:345 +#: setting.py:352 msgid "API keys will auto-switch if the previous one is unavailable." msgstr "" -#: setting.py:355 +#: setting.py:362 msgid "Preferred Language" msgstr "" -#: setting.py:366 +#: setting.py:373 msgid "HTTP Request" msgstr "" -#: setting.py:380 +#: setting.py:387 msgid "Concurrency limit" msgstr "" -#: setting.py:381 +#: setting.py:388 msgid "Interval (seconds)" msgstr "" -#: setting.py:382 +#: setting.py:389 msgid "Attempt times" msgstr "" -#: setting.py:383 +#: setting.py:390 msgid "Timeout (seconds)" msgstr "" -#: setting.py:385 +#: setting.py:392 msgid "Error count to stop translation" msgstr "" -#: setting.py:395 +#: setting.py:402 msgid "Tune Gemini" msgstr "" -#: setting.py:402 setting.py:436 +#: setting.py:409 setting.py:443 msgid "Prompt" msgstr "" -#: setting.py:428 +#: setting.py:435 msgid "Tune ChatGPT" msgstr "" -#: setting.py:438 +#: setting.py:445 msgid "Endpoint" msgstr "" -#: setting.py:447 +#: setting.py:454 msgid "Model" msgstr "" -#: setting.py:470 +#: setting.py:477 msgid "Sampling" msgstr "" -#: setting.py:475 +#: setting.py:482 msgid "Enable streaming text like in ChatGPT" msgstr "" -#: setting.py:476 +#: setting.py:483 msgid "Stream" msgstr "" -#: setting.py:693 +#: setting.py:700 msgid "Below original" msgstr "" -#: setting.py:695 +#: setting.py:702 msgid "Above original" msgstr "" -#: setting.py:697 setting.py:699 +#: setting.py:704 setting.py:706 msgid "Beta" msgstr "" -#: setting.py:697 +#: setting.py:704 msgid "Left to original" msgstr "" -#: setting.py:699 +#: setting.py:706 msgid "Right to original" msgstr "" -#: setting.py:700 +#: setting.py:707 msgid "Add without original" msgstr "" -#: setting.py:719 +#: setting.py:726 msgid "Translation" msgstr "" -#: setting.py:730 +#: setting.py:737 msgid "Translation Position" msgstr "" -#: setting.py:778 +#: setting.py:788 msgid "Original Color" msgstr "" -#: setting.py:783 setting.py:801 +#: setting.py:793 setting.py:811 msgid "CSS color value, e.g., #666666, grey, rgb(80, 80, 80)" msgstr "" -#: setting.py:797 +#: setting.py:807 msgid "Translation Color" msgstr "" -#: setting.py:852 +#: setting.py:862 msgid "Translation Glossary" msgstr "" -#: setting.py:856 +#: setting.py:866 msgid "Choose a glossary file" msgstr "" -#: setting.py:875 +#: setting.py:885 msgid "Ignore Paragraph" msgstr "" -#: setting.py:881 +#: setting.py:891 msgid "Scope" msgstr "" -#: setting.py:882 +#: setting.py:892 msgid "Text only" msgstr "" -#: setting.py:884 +#: setting.py:894 msgid "HTML element" msgstr "" -#: setting.py:891 +#: setting.py:901 msgid "Mode" msgstr "" -#: setting.py:892 +#: setting.py:902 msgid "Normal" msgstr "" -#: setting.py:894 +#: setting.py:904 msgid "Normal (case-sensitive)" msgstr "" -#: setting.py:895 +#: setting.py:905 msgid "Regular Expression" msgstr "" -#: setting.py:937 +#: setting.py:947 msgid "Exclude paragraph by keyword. One keyword per line:" msgstr "" -#: setting.py:938 +#: setting.py:948 msgid "Exclude paragraph by case-sensitive keyword. One keyword per line:" msgstr "" -#: setting.py:940 +#: setting.py:950 msgid "Exclude paragraph by regular expression pattern. One pattern per line:" msgstr "" -#: setting.py:956 +#: setting.py:966 msgid "Ignore Element" msgstr "" -#: setting.py:964 +#: setting.py:974 msgid "CSS selectors to exclude elements. One rule per line:" msgstr "" -#: setting.py:967 +#: setting.py:977 msgid "e.g." msgstr "" -#: setting.py:971 +#: setting.py:981 msgid "Ebook Metadata" msgstr "" -#: setting.py:974 +#: setting.py:984 msgid "Set \"Target Language\" to metadata" msgstr "" -#: setting.py:977 +#: setting.py:987 msgid "Subjects of ebook (one subject per line)" msgstr "" -#: setting.py:979 +#: setting.py:989 msgid "Subject" msgstr "" -#: setting.py:996 setting.py:1031 +#: setting.py:1006 setting.py:1041 msgid "Proxy host or port is incorrect." msgstr "" -#: setting.py:998 +#: setting.py:1008 msgid "The proxy is available." msgstr "" -#: setting.py:999 +#: setting.py:1009 msgid "The proxy is not available." msgstr "" -#: setting.py:1088 +#: setting.py:1098 msgid "the prompt must include {}." msgstr "" -#: setting.py:1117 setting.py:1124 +#: setting.py:1127 setting.py:1134 msgid "Invalid color value." msgstr "" -#: setting.py:1133 +#: setting.py:1143 msgid "The specified glossary file does not exist." msgstr "" -#: setting.py:1145 +#: setting.py:1155 msgid "{} is not a valid regular expression." msgstr "" -#: setting.py:1157 +#: setting.py:1167 msgid "{} is not a valid CSS seletor." msgstr "" diff --git a/translations/pt.po b/translations/pt.po index 11dd0f4..e98e186 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-03-10 12:31+0800\n" +"POT-Creation-Date: 2024-03-11 21:06+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: none\n" @@ -108,8 +108,16 @@ msgstr "Ebook de Saída" msgid "Output" msgstr "Saída" -msgid "Title" -msgstr "Título" +msgid "Filename" +msgstr "Nome do arquivo" + +msgid "Format" +msgstr "" + +msgid "" +"The ebook's filename is automatically managed by Calibre according to " +"metadata since the output path is set to Calibre Library." +msgstr "" msgid "Translated" msgstr "Traduzido" @@ -147,6 +155,9 @@ msgstr "Você tem certeza de que deseja parar o processo de tradução?" msgid "Output Format" msgstr "Formato de Saída" +msgid "Title" +msgstr "Título" + msgid "Translate" msgstr "Traduzir" @@ -207,9 +218,6 @@ msgstr "Idioma" msgid "Merge Length" msgstr "Comprimento da Mesclagem" -msgid "Filename" -msgstr "Nome do arquivo" - msgid "Size (MB)" msgstr "Tamanho (MB)" diff --git a/translations/zh_CN.mo b/translations/zh_CN.mo index acf74958d506559077df6548fddfd3700e95b0a8..82d5722c1a8e7272cfa53c116d77e7f607b33459 100644 GIT binary patch delta 5206 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 M-BEsc$g9!+2fKiCKL7v# delta 4904 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 URbJOI!BrJKc7|3R%GuNSzuS4_c>n+a diff --git a/translations/zh_CN.po b/translations/zh_CN.po index 2f0ae32..7caedb6 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-03-10 12:31+0800\n" +"POT-Creation-Date: 2024-03-11 21:06+0800\n" "PO-Revision-Date: 2023-04-17 14:17+0800\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -106,8 +106,16 @@ msgstr "输出电子书" msgid "Output" msgstr "输出" -msgid "Title" -msgstr "书名" +msgid "Filename" +msgstr "文件名" + +msgid "Format" +msgstr "格式" + +msgid "" +"The ebook's filename is automatically managed by Calibre according to " +"metadata since the output path is set to Calibre Library." +msgstr "由于输出路径是 Calibre 书库,因此电子书文件名由 Calibre 根据原数据自动处理。" msgid "Translated" msgstr "已翻译" @@ -145,6 +153,9 @@ msgstr "你确定要停止翻译过程吗?" msgid "Output Format" msgstr "输出格式" +msgid "Title" +msgstr "书名" + msgid "Translate" msgstr "翻译" @@ -203,9 +214,6 @@ msgstr "语言" msgid "Merge Length" msgstr "合并长度" -msgid "Filename" -msgstr "文件名" - msgid "Size (MB)" msgstr "大小(MB)" @@ -246,7 +254,7 @@ msgid "Ebook Specific" msgstr "自动选择" msgid "Donate" -msgstr "赞赏" +msgstr "捐助" msgid "Feedback" msgstr "反馈" @@ -715,6 +723,18 @@ msgstr "缓存管理器" msgid "Choose Translation Mode" msgstr "选择翻译模式" +#~ msgid "" +#~ "The ebook's filename is automatically managed by Calibre according to " +#~ "metadata“since the output path is set to Calibre Library." +#~ msgstr "" +#~ "由于输出路径是 Calibre 书库,因此电子书文件名由 Calibre 根据原数据自动处" +#~ "理。" + +#~ msgid "" +#~ "The ebook's filename is managed by Calibre since the output path is set " +#~ "to Calibre Library." +#~ msgstr "由于输出路径是 Calibre 书库,因此电子书文件名由 Calibre 管理。" + #~ msgid "Add after original" #~ msgstr "加在原文后" diff --git a/translations/zh_TW.po b/translations/zh_TW.po index ae16582..9df2676 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-03-10 12:31+0800\n" +"POT-Creation-Date: 2024-03-11 21:06+0800\n" "PO-Revision-Date: 2023-04-25 15:36+0800\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" @@ -107,8 +107,16 @@ msgstr "" msgid "Output" msgstr "" -msgid "Title" -msgstr "書名" +msgid "Filename" +msgstr "" + +msgid "Format" +msgstr "" + +msgid "" +"The ebook's filename is automatically managed by Calibre according to " +"metadata since the output path is set to Calibre Library." +msgstr "" msgid "Translated" msgstr "" @@ -146,6 +154,9 @@ msgstr "" msgid "Output Format" msgstr "輸出格式" +msgid "Title" +msgstr "書名" + msgid "Translate" msgstr "翻譯" @@ -204,9 +215,6 @@ msgstr "" msgid "Merge Length" msgstr "" -msgid "Filename" -msgstr "" - msgid "Size (MB)" msgstr ""