epub-translator 0.1.4__tar.gz → 0.1.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. {epub_translator-0.1.4 → epub_translator-0.1.5}/PKG-INFO +30 -7
  2. {epub_translator-0.1.4 → epub_translator-0.1.5}/README.md +29 -6
  3. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml_translator/submitter.py +28 -10
  4. {epub_translator-0.1.4 → epub_translator-0.1.5}/pyproject.toml +1 -1
  5. {epub_translator-0.1.4 → epub_translator-0.1.5}/LICENSE +0 -0
  6. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/__init__.py +0 -0
  7. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/data/fill.jinja +0 -0
  8. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/data/mmltex/README.md +0 -0
  9. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/data/mmltex/cmarkup.xsl +0 -0
  10. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/data/mmltex/entities.xsl +0 -0
  11. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/data/mmltex/glayout.xsl +0 -0
  12. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/data/mmltex/mmltex.xsl +0 -0
  13. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/data/mmltex/scripts.xsl +0 -0
  14. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/data/mmltex/tables.xsl +0 -0
  15. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/data/mmltex/tokens.xsl +0 -0
  16. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/data/translate.jinja +0 -0
  17. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/epub/__init__.py +0 -0
  18. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/epub/common.py +0 -0
  19. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/epub/math.py +0 -0
  20. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/epub/metadata.py +0 -0
  21. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/epub/spines.py +0 -0
  22. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/epub/toc.py +0 -0
  23. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/epub/zip.py +0 -0
  24. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/epub_transcode.py +0 -0
  25. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/language.py +0 -0
  26. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/llm/__init__.py +0 -0
  27. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/llm/context.py +0 -0
  28. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/llm/core.py +0 -0
  29. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/llm/error.py +0 -0
  30. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/llm/executor.py +0 -0
  31. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/llm/increasable.py +0 -0
  32. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/llm/types.py +0 -0
  33. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/punctuation.py +0 -0
  34. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/segment/__init__.py +0 -0
  35. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/segment/block_segment.py +0 -0
  36. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/segment/common.py +0 -0
  37. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/segment/inline_segment.py +0 -0
  38. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/segment/text_segment.py +0 -0
  39. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/segment/utils.py +0 -0
  40. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/serial/__init__.py +0 -0
  41. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/serial/chunk.py +0 -0
  42. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/serial/segment.py +0 -0
  43. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/serial/splitter.py +0 -0
  44. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/template.py +0 -0
  45. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/translator.py +0 -0
  46. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/utils.py +0 -0
  47. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml/__init__.py +0 -0
  48. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml/const.py +0 -0
  49. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml/deduplication.py +0 -0
  50. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml/firendly/__init__.py +0 -0
  51. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml/firendly/decoder.py +0 -0
  52. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml/firendly/encoder.py +0 -0
  53. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml/firendly/parser.py +0 -0
  54. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml/firendly/tag.py +0 -0
  55. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml/firendly/transform.py +0 -0
  56. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml/inline.py +0 -0
  57. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml/self_closing.py +0 -0
  58. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml/utils.py +0 -0
  59. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml/xml.py +0 -0
  60. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml/xml_like.py +0 -0
  61. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml_interrupter.py +0 -0
  62. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml_translator/__init__.py +0 -0
  63. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml_translator/callbacks.py +0 -0
  64. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml_translator/common.py +0 -0
  65. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml_translator/hill_climbing.py +0 -0
  66. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml_translator/stream_mapper.py +0 -0
  67. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml_translator/translator.py +0 -0
  68. {epub_translator-0.1.4 → epub_translator-0.1.5}/epub_translator/xml_translator/validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: epub-translator
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: Translate the epub book using LLM. The translated book will retain the original text and list the translated text side by side with the original text.
5
5
  License: MIT
6
6
  Keywords: epub,llm,translation,translator
@@ -238,16 +238,17 @@ translate(
238
238
 
239
239
  ### Error Handling with `on_fill_failed`
240
240
 
241
- Monitor and handle translation errors using the `on_fill_failed` callback:
241
+ Monitor translation errors using the `on_fill_failed` callback. The system automatically retries failed translations up to `max_retries` times (default: 5). Most errors are recovered during retries and don't affect the final output.
242
242
 
243
243
  ```python
244
244
  from epub_translator import FillFailedEvent
245
245
 
246
246
  def handle_fill_error(event: FillFailedEvent):
247
- print(f"Translation error (attempt {event.retried_count}):")
248
- print(f" {event.error_message}")
247
+ # Only log critical errors that will affect the final EPUB
249
248
  if event.over_maximum_retries:
250
- print(" Maximum retries exceeded!")
249
+ print(f"Critical error after {event.retried_count} attempts:")
250
+ print(f" {event.error_message}")
251
+ print(" This error will be present in the final EPUB file!")
251
252
 
252
253
  translate(
253
254
  source_path="source.epub",
@@ -259,10 +260,32 @@ translate(
259
260
  )
260
261
  ```
261
262
 
263
+ **Understanding Error Severity:**
264
+
262
265
  The `FillFailedEvent` contains:
263
266
  - `error_message: str` - Description of the error
264
- - `retried_count: int` - Current retry attempt number
265
- - `over_maximum_retries: bool` - Whether max retries has been exceeded
267
+ - `retried_count: int` - Current retry attempt number (1 to max_retries)
268
+ - `over_maximum_retries: bool` - Whether the error is critical
269
+
270
+ **Error Categories:**
271
+
272
+ - **Recoverable errors** (`over_maximum_retries=False`): Errors during retry attempts. The system will continue retrying and may resolve these automatically. Safe to ignore in most cases.
273
+
274
+ - **Critical errors** (`over_maximum_retries=True`): Errors that persist after all retry attempts. These will appear in the final EPUB file and should be investigated.
275
+
276
+ **Advanced Usage:**
277
+
278
+ For verbose logging during translation debugging:
279
+
280
+ ```python
281
+ def handle_fill_error(event: FillFailedEvent):
282
+ if event.over_maximum_retries:
283
+ # Critical: affects final output
284
+ print(f"❌ CRITICAL: {event.error_message}")
285
+ else:
286
+ # Informational: system is retrying
287
+ print(f"⚠️ Retry {event.retried_count}: {event.error_message}")
288
+ ```
266
289
 
267
290
  ### Dual-LLM Architecture
268
291
 
@@ -205,16 +205,17 @@ translate(
205
205
 
206
206
  ### Error Handling with `on_fill_failed`
207
207
 
208
- Monitor and handle translation errors using the `on_fill_failed` callback:
208
+ Monitor translation errors using the `on_fill_failed` callback. The system automatically retries failed translations up to `max_retries` times (default: 5). Most errors are recovered during retries and don't affect the final output.
209
209
 
210
210
  ```python
211
211
  from epub_translator import FillFailedEvent
212
212
 
213
213
  def handle_fill_error(event: FillFailedEvent):
214
- print(f"Translation error (attempt {event.retried_count}):")
215
- print(f" {event.error_message}")
214
+ # Only log critical errors that will affect the final EPUB
216
215
  if event.over_maximum_retries:
217
- print(" Maximum retries exceeded!")
216
+ print(f"Critical error after {event.retried_count} attempts:")
217
+ print(f" {event.error_message}")
218
+ print(" This error will be present in the final EPUB file!")
218
219
 
219
220
  translate(
220
221
  source_path="source.epub",
@@ -226,10 +227,32 @@ translate(
226
227
  )
227
228
  ```
228
229
 
230
+ **Understanding Error Severity:**
231
+
229
232
  The `FillFailedEvent` contains:
230
233
  - `error_message: str` - Description of the error
231
- - `retried_count: int` - Current retry attempt number
232
- - `over_maximum_retries: bool` - Whether max retries has been exceeded
234
+ - `retried_count: int` - Current retry attempt number (1 to max_retries)
235
+ - `over_maximum_retries: bool` - Whether the error is critical
236
+
237
+ **Error Categories:**
238
+
239
+ - **Recoverable errors** (`over_maximum_retries=False`): Errors during retry attempts. The system will continue retrying and may resolve these automatically. Safe to ignore in most cases.
240
+
241
+ - **Critical errors** (`over_maximum_retries=True`): Errors that persist after all retry attempts. These will appear in the final EPUB file and should be investigated.
242
+
243
+ **Advanced Usage:**
244
+
245
+ For verbose logging during translation debugging:
246
+
247
+ ```python
248
+ def handle_fill_error(event: FillFailedEvent):
249
+ if event.over_maximum_retries:
250
+ # Critical: affects final output
251
+ print(f"❌ CRITICAL: {event.error_message}")
252
+ else:
253
+ # Informational: system is retrying
254
+ print(f"⚠️ Retry {event.retried_count}: {event.error_message}")
255
+ ```
233
256
 
234
257
  ### Dual-LLM Architecture
235
258
 
@@ -122,11 +122,17 @@ class _Submitter:
122
122
  last_tail_element = child_element
123
123
 
124
124
  for text_segments, child_node in node.items:
125
- tail_element = tail_elements.get(id(child_node.raw_element), None)
125
+ anchor_element = _find_anchor_in_parent(node.raw_element, child_node.raw_element)
126
+ if anchor_element is None:
127
+ # 防御性编程:理论上 anchor_element 不应该为 None,
128
+ # 因为 _nest_nodes 已经通过 _check_includes 验证了包含关系。
129
+ continue
130
+
131
+ tail_element = tail_elements.get(id(anchor_element), None)
126
132
  items_preserved_elements: list[Element] = []
127
133
 
128
134
  if self._action == SubmitKind.REPLACE:
129
- end_index = index_of_parent(node.raw_element, child_node.raw_element)
135
+ end_index = index_of_parent(node.raw_element, anchor_element)
130
136
  items_preserved_elements = self._remove_elements_after_tail(
131
137
  node_element=node.raw_element,
132
138
  tail_element=tail_element,
@@ -137,11 +143,11 @@ class _Submitter:
137
143
  node_element=node.raw_element,
138
144
  text_segments=text_segments,
139
145
  tail_element=tail_element,
146
+ anchor_element=anchor_element,
140
147
  append_to_end=False,
141
- ref_element=child_node.raw_element,
142
148
  )
143
149
  if items_preserved_elements:
144
- insert_position = index_of_parent(node.raw_element, child_node.raw_element)
150
+ insert_position = index_of_parent(node.raw_element, anchor_element)
145
151
  for i, elem in enumerate(items_preserved_elements):
146
152
  node.raw_element.insert(insert_position + i, elem)
147
153
 
@@ -166,7 +172,7 @@ class _Submitter:
166
172
  node_element=node.raw_element,
167
173
  text_segments=node.tail_text_segments,
168
174
  tail_element=last_tail_element,
169
- ref_element=None,
175
+ anchor_element=None,
170
176
  append_to_end=True,
171
177
  )
172
178
  if tail_preserved_elements:
@@ -208,7 +214,7 @@ class _Submitter:
208
214
  node_element: Element,
209
215
  text_segments: list[TextSegment],
210
216
  tail_element: Element | None,
211
- ref_element: Element | None,
217
+ anchor_element: Element | None,
212
218
  append_to_end: bool,
213
219
  ) -> None:
214
220
  combined = self._combine_text_segments(text_segments)
@@ -225,14 +231,14 @@ class _Submitter:
225
231
  append_text=combined.text,
226
232
  will_inject_space=will_inject_space,
227
233
  )
228
- elif ref_element is None:
234
+ elif anchor_element is None:
229
235
  node_element.text = self._append_text_in_element(
230
236
  origin_text=node_element.text,
231
237
  append_text=combined.text,
232
238
  will_inject_space=will_inject_space,
233
239
  )
234
240
  else:
235
- ref_index = index_of_parent(node_element, ref_element)
241
+ ref_index = index_of_parent(node_element, anchor_element)
236
242
  if ref_index > 0:
237
243
  # 添加到前一个元素的 tail
238
244
  prev_element = node_element[ref_index - 1]
@@ -253,10 +259,10 @@ class _Submitter:
253
259
  insert_position = index_of_parent(node_element, tail_element) + 1
254
260
  elif append_to_end:
255
261
  insert_position = len(node_element)
256
- elif ref_element is not None:
262
+ elif anchor_element is not None:
257
263
  # 使用 ref_element 来定位插入位置
258
264
  # 如果文本被添加到前一个元素的 tail,则在前一个元素之后插入
259
- ref_index = index_of_parent(node_element, ref_element)
265
+ ref_index = index_of_parent(node_element, anchor_element)
260
266
  if ref_index > 0:
261
267
  # 在前一个元素之后插入
262
268
  insert_position = ref_index
@@ -346,6 +352,18 @@ def _nest_nodes(mappings: list[InlineSegmentMapping]) -> Generator[_Node, None,
346
352
  yield child_node
347
353
 
348
354
 
355
+ def _find_anchor_in_parent(parent: Element, descendant: Element) -> Element | None:
356
+ for child in parent:
357
+ if child is descendant:
358
+ return descendant
359
+
360
+ for child in parent:
361
+ if _check_includes(child, descendant):
362
+ return child
363
+
364
+ return None
365
+
366
+
349
367
  def _fold_top_of_stack(stack: list[_Node]):
350
368
  child_node = stack.pop()
351
369
  if not stack:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "epub-translator"
3
- version = "0.1.4"
3
+ version = "0.1.5"
4
4
  description = "Translate the epub book using LLM. The translated book will retain the original text and list the translated text side by side with the original text."
5
5
  keywords = ["epub", "llm", "translation", "translator"]
6
6
  authors = [
File without changes