BabelDOC 0.3.39__tar.gz → 0.3.40__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 (106) hide show
  1. {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/workflows/publish-to-pypi.yml +2 -2
  2. {babeldoc-0.3.39 → babeldoc-0.3.40}/PKG-INFO +2 -1
  3. {babeldoc-0.3.39 → babeldoc-0.3.40}/README.md +1 -0
  4. babeldoc-0.3.40/babeldoc/__init__.py +1 -0
  5. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/const.py +1 -1
  6. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/add_debug_information.py +1 -1
  7. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/detect_scanned_file.py +1 -1
  8. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/il_translator.py +107 -9
  9. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/il_translator_llm_only.py +58 -15
  10. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/layout_parser.py +1 -1
  11. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/styles_and_formulas.py +1 -0
  12. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/table_parser.py +1 -1
  13. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/translator/translator.py +1 -1
  14. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/utils/style_helper.py +1 -1
  15. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/high_level.py +2 -2
  16. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/main.py +1 -1
  17. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/translation_config.py +2 -0
  18. {babeldoc-0.3.39 → babeldoc-0.3.40}/pyproject.toml +2 -2
  19. {babeldoc-0.3.39 → babeldoc-0.3.40}/uv.lock +1 -1
  20. babeldoc-0.3.39/babeldoc/__init__.py +0 -1
  21. {babeldoc-0.3.39 → babeldoc-0.3.40}/.cursorignore +0 -0
  22. {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
  23. {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/ISSUE_TEMPLATE/feature_request.yaml +0 -0
  24. {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/PULL_REQUEST_TEMPLATE/pr_form.yml +0 -0
  25. {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/dependabot.yml +0 -0
  26. {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/labels.yml +0 -0
  27. {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/release-drafter.yml +0 -0
  28. {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/workflows/codeql.yml +0 -0
  29. {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/workflows/docs.yml +0 -0
  30. {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/workflows/labeler.yml +0 -0
  31. {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/workflows/lint.yml +0 -0
  32. {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/workflows/pr-lint.yml +0 -0
  33. {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/workflows/test.yml +0 -0
  34. {babeldoc-0.3.39 → babeldoc-0.3.40}/.gitignore +0 -0
  35. {babeldoc-0.3.39 → babeldoc-0.3.40}/.pre-commit-config.yaml +0 -0
  36. {babeldoc-0.3.39 → babeldoc-0.3.40}/LICENSE +0 -0
  37. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/assets/assets.py +0 -0
  38. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/assets/embedding_assets_metadata.py +0 -0
  39. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/asynchronize/__init__.py +0 -0
  40. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/converter.py +0 -0
  41. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/__init__.py +0 -0
  42. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/babeldoc_exception/BabelDOCException.py +0 -0
  43. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/backend/__init__.py +0 -0
  44. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/backend/pdf_creater.py +0 -0
  45. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/frontend/__init__.py +0 -0
  46. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/frontend/il_creater.py +0 -0
  47. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/il_version_1.py +0 -0
  48. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/il_version_1.rnc +0 -0
  49. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/il_version_1.rng +0 -0
  50. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/il_version_1.xsd +0 -0
  51. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/__init__.py +0 -0
  52. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/paragraph_finder.py +0 -0
  53. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/remove_descent.py +0 -0
  54. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/typesetting.py +0 -0
  55. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/translator/__init__.py +0 -0
  56. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/translator/cache.py +0 -0
  57. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/utils/atomic_integer.py +0 -0
  58. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/utils/fontmap.py +0 -0
  59. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/utils/layout_helper.py +0 -0
  60. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/utils/paragraph_helper.py +0 -0
  61. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/utils/priority_thread_pool_executor.py +0 -0
  62. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/utils/zstd_helper.py +0 -0
  63. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/xml_converter.py +0 -0
  64. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/docvision/README.md +0 -0
  65. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/docvision/__init__.py +0 -0
  66. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/docvision/doclayout.py +0 -0
  67. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/docvision/rpc_doclayout.py +0 -0
  68. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/docvision/table_detection/rapidocr.py +0 -0
  69. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/format/office/__init__.py +0 -0
  70. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/pdfinterp.py +0 -0
  71. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/progress_monitor.py +0 -0
  72. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/result_merger.py +0 -0
  73. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/split_manager.py +0 -0
  74. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/tools/generate_font_metadata.py +0 -0
  75. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/tools/italic_assistance.py +0 -0
  76. {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/tools/italic_recognize_tool.py +0 -0
  77. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/CODE_OF_CONDUCT.md +0 -0
  78. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/CONTRIBUTING.md +0 -0
  79. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/CONTRIBUTOR_REWARD.md +0 -0
  80. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md +0 -0
  81. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/ILTranslator/ILTranslator.md +0 -0
  82. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/PDFCreation/PDFCreation.md +0 -0
  83. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/PDFParsing/PDFParsing.md +0 -0
  84. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md +0 -0
  85. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/README.md +0 -0
  86. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/StylesAndFormulas/StylesAndFormulas.md +0 -0
  87. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/Typesetting/Typesetting.md +0 -0
  88. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/README.md +0 -0
  89. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/deploy.sh +0 -0
  90. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-banner.png +0 -0
  91. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.png +0 -0
  92. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.svg +0 -0
  93. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-big-logo-with-transparent-background.png +0 -0
  94. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-big-logo-with-transparent-background.svg +0 -0
  95. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-big-logo.png +0 -0
  96. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-contributor_reward_example.png +0 -0
  97. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-preview.gif +0 -0
  98. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-small-logo-with-transparent-background.png +0 -0
  99. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-small-logo-with-transparent-background.svg +0 -0
  100. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-small-logo.png +0 -0
  101. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/index.md +0 -0
  102. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/intro-to-pdf-object.md +0 -0
  103. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/requirements.txt +0 -0
  104. {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/supported_languages.md +0 -0
  105. {babeldoc-0.3.39 → babeldoc-0.3.40}/mkdocs.yml +0 -0
  106. {babeldoc-0.3.39 → babeldoc-0.3.40}/tests/test_translation_config.py +0 -0
@@ -61,8 +61,8 @@ jobs:
61
61
  - name: Bump version for developmental release
62
62
  if: "! steps.check-version.outputs.tag"
63
63
  run: |
64
- version=$(bumpver update --patch --tag=final --dry 2>&1 | grep "New Version" | awk '{print $NF}') &&
65
- bumpver update --set-version $version.dev$(date +%s)
64
+ version=$(uv run bumpver update --patch --tag=final --dry 2>&1 | grep "New Version" | awk '{print $NF}') &&
65
+ uv run bumpver update --set-version $version.dev$(date +%s)
66
66
 
67
67
  - name: Build package
68
68
  run: "uv build"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: BabelDOC
3
- Version: 0.3.39
3
+ Version: 0.3.40
4
4
  Summary: Yet Another Document Translator
5
5
  Project-URL: Homepage, https://github.com/funstory-ai/BabelDOC
6
6
  Project-URL: Issues, https://github.com/funstory-ai/BabelDOC/issues
@@ -216,6 +216,7 @@ uv run babeldoc --files example.pdf --files example2.pdf --openai --openai-model
216
216
  - `--min-text-length`: Minimum text length to translate (default: 5)
217
217
  - `--openai`: Use OpenAI for translation (default: False)
218
218
  - `--custom-system-prompt`: Custom system prompt for translation.
219
+ - `--add-formula-placehold-hint`: Add formula placeholder hint for translation. (Currently not recommended, it may affect translation quality, default: False)
219
220
 
220
221
  > [!TIP]
221
222
  >
@@ -167,6 +167,7 @@ uv run babeldoc --files example.pdf --files example2.pdf --openai --openai-model
167
167
  - `--min-text-length`: Minimum text length to translate (default: 5)
168
168
  - `--openai`: Use OpenAI for translation (default: False)
169
169
  - `--custom-system-prompt`: Custom system prompt for translation.
170
+ - `--add-formula-placehold-hint`: Add formula placeholder hint for translation. (Currently not recommended, it may affect translation quality, default: False)
170
171
 
171
172
  > [!TIP]
172
173
  >
@@ -0,0 +1 @@
1
+ __version__ = "0.3.40"
@@ -3,7 +3,7 @@ import shutil
3
3
  import subprocess
4
4
  from pathlib import Path
5
5
 
6
- __version__ = "0.3.39"
6
+ __version__ = "0.3.40"
7
7
 
8
8
  CACHE_FOLDER = Path.home() / ".cache" / "babeldoc"
9
9
 
@@ -34,7 +34,7 @@ class AddDebugInformation:
34
34
 
35
35
  def _create_text(self, text: str, color: GraphicState, box: il_version_1.Box):
36
36
  style = il_version_1.PdfStyle(
37
- font_id="china-ss",
37
+ font_id="base",
38
38
  font_size=4,
39
39
  graphic_state=color,
40
40
  )
@@ -31,7 +31,7 @@ class DetectScannedFile:
31
31
  # Note: PDF coordinates are from bottom-left,
32
32
  # so we use y2 for top position
33
33
  style = il_version_1.PdfStyle(
34
- font_id="china-ss",
34
+ font_id="base",
35
35
  font_size=4,
36
36
  graphic_state=color,
37
37
  )
@@ -1,3 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
1
4
  import json
2
5
  import logging
3
6
  import re
@@ -78,6 +81,10 @@ class DocumentTranslateTracker:
78
81
  i_str = getattr(para, "input", None)
79
82
  o_str = getattr(para, "output", None)
80
83
  pdf_unicode = getattr(para, "pdf_unicode", None)
84
+ llm_translate_trackers = getattr(para, "llm_translate_trackers", None)
85
+ llm_translate_trackers_json = []
86
+ for tracker in llm_translate_trackers:
87
+ llm_translate_trackers_json.append(tracker.to_dict())
81
88
  if pdf_unicode is None or i_str is None:
82
89
  continue
83
90
  paragraphs.append(
@@ -85,6 +92,7 @@ class DocumentTranslateTracker:
85
92
  "input": i_str,
86
93
  "output": o_str,
87
94
  "pdf_unicode": pdf_unicode,
95
+ "llm_translate_trackers": llm_translate_trackers_json,
88
96
  },
89
97
  )
90
98
  pages.append({"paragraph": paragraphs})
@@ -103,7 +111,7 @@ class PageTranslateTracker:
103
111
 
104
112
  class ParagraphTranslateTracker:
105
113
  def __init__(self):
106
- pass
114
+ self.llm_translate_trackers = []
107
115
 
108
116
  def set_pdf_unicode(self, unicode: str):
109
117
  self.pdf_unicode = unicode
@@ -114,6 +122,52 @@ class ParagraphTranslateTracker:
114
122
  def set_output(self, output: str):
115
123
  self.output = output
116
124
 
125
+ def new_llm_translate_tracker(self) -> LLMTranslateTracker:
126
+ tracker = LLMTranslateTracker()
127
+ self.llm_translate_trackers.append(tracker)
128
+ return tracker
129
+
130
+ def last_llm_translate_tracker(self) -> LLMTranslateTracker | None:
131
+ if self.llm_translate_trackers:
132
+ return self.llm_translate_trackers[-1]
133
+ return None
134
+
135
+
136
+ class LLMTranslateTracker:
137
+ def __init__(self):
138
+ self.input = ""
139
+ self.output = ""
140
+ self.has_error = False
141
+ self.error_message = ""
142
+ self.placeholder_full_match = False
143
+ self.fallback_to_translate = False
144
+
145
+ def set_input(self, input_text: str):
146
+ self.input = input_text
147
+
148
+ def set_output(self, output_text: str):
149
+ self.output = output_text
150
+
151
+ def set_error_message(self, error_message: str):
152
+ self.has_error = True
153
+ self.error_message = error_message
154
+
155
+ def set_placeholder_full_match(self):
156
+ self.placeholder_full_match = True
157
+
158
+ def set_fallback_to_translate(self):
159
+ self.fallback_to_translate = True
160
+
161
+ def to_dict(self):
162
+ return {
163
+ "input": self.input,
164
+ "output": self.output,
165
+ "has_error": self.has_error,
166
+ "error_message": self.error_message,
167
+ "placeholder_full_match": self.placeholder_full_match,
168
+ "fallback_to_translate": self.fallback_to_translate,
169
+ }
170
+
117
171
 
118
172
  class ILTranslator:
119
173
  stage_name = "Translate Paragraphs"
@@ -156,9 +210,11 @@ class ILTranslator:
156
210
  # Try to find the first title paragraph
157
211
  title_paragraph = self.find_title_paragraph(docs)
158
212
  self.translation_config.shared_context_cross_split_part.first_paragraph = (
213
+ copy.deepcopy(title_paragraph)
214
+ )
215
+ self.translation_config.shared_context_cross_split_part.recent_title_paragraph = copy.deepcopy(
159
216
  title_paragraph
160
217
  )
161
- self.translation_config.shared_context_cross_split_part.recent_title_paragraph = title_paragraph
162
218
  if title_paragraph:
163
219
  logger.info(f"Found first title paragraph: {title_paragraph.unicode}")
164
220
 
@@ -220,7 +276,9 @@ class ILTranslator:
220
276
  # self.translate_paragraph(paragraph, pbar,tracker.new_paragraph(), page_font_map, page_xobj_font_map)
221
277
  paragraph_token_count = self.calc_token_count(paragraph.unicode)
222
278
  if paragraph.layout_label == "title":
223
- self.shared_context_cross_split_part.recent_title_paragraph = paragraph
279
+ self.shared_context_cross_split_part.recent_title_paragraph = (
280
+ copy.deepcopy(paragraph)
281
+ )
224
282
  executor.submit(
225
283
  self.translate_paragraph,
226
284
  paragraph,
@@ -245,6 +303,24 @@ class ILTranslator:
245
303
  self.placeholders = placeholders
246
304
  self.base_style = base_style
247
305
 
306
+ def get_placeholders_hint(self) -> dict[str, str] | None:
307
+ hint = {}
308
+ for placeholder in self.placeholders:
309
+ if isinstance(placeholder, FormulaPlaceholder):
310
+ cid_count = 0
311
+ for char in placeholder.formula.pdf_character:
312
+ if re.match(r"^\(cid:\d+\)$", char.char_unicode):
313
+ cid_count += 1
314
+ if cid_count > len(placeholder.formula.pdf_character) * 0.8:
315
+ continue
316
+
317
+ hint[placeholder.placeholder] = get_char_unicode_string(
318
+ placeholder.formula.pdf_character
319
+ )
320
+ if hint:
321
+ return hint
322
+ return None
323
+
248
324
  def create_formula_placeholder(
249
325
  self,
250
326
  formula: PdfFormula,
@@ -452,9 +528,8 @@ class ILTranslator:
452
528
  self,
453
529
  input_text: TranslateInput,
454
530
  output: str,
531
+ llm_translate_tracker: LLMTranslateTracker | None = None,
455
532
  ) -> [PdfParagraphComposition]:
456
- import re
457
-
458
533
  result = []
459
534
 
460
535
  # 如果没有占位符,直接返回整个文本
@@ -463,6 +538,8 @@ class ILTranslator:
463
538
  comp.pdf_same_style_unicode_characters = PdfSameStyleUnicodeCharacters()
464
539
  comp.pdf_same_style_unicode_characters.unicode = output
465
540
  comp.pdf_same_style_unicode_characters.pdf_style = input_text.base_style
541
+ if llm_translate_tracker:
542
+ llm_translate_tracker.set_placeholder_full_match()
466
543
  return [comp]
467
544
 
468
545
  # 构建正则表达式模式
@@ -484,7 +561,14 @@ class ILTranslator:
484
561
  placeholder_patterns.append(f"({left})")
485
562
  placeholder_patterns.append(f"({right})")
486
563
  placeholder_map[placeholder.left_placeholder] = placeholder
487
-
564
+ all_match = True
565
+ for pattern in patterns:
566
+ if not re.search(pattern, output, flags=re.IGNORECASE):
567
+ all_match = False
568
+ break
569
+ if all_match:
570
+ if llm_translate_tracker:
571
+ llm_translate_tracker.set_placeholder_full_match()
488
572
  # 合并所有模式
489
573
  combined_pattern = "|".join(patterns)
490
574
  combined_placeholder_pattern = "|".join(placeholder_patterns)
@@ -626,10 +710,12 @@ class ILTranslator:
626
710
  """Post-translation processing: update paragraph with translated text."""
627
711
  tracker.set_output(translated_text)
628
712
  if translated_text == translate_input.unicode:
713
+ if llm_translate_tracker := tracker.last_llm_translate_tracker():
714
+ llm_translate_tracker.set_placeholder_full_match()
629
715
  return False
630
716
  paragraph.unicode = translated_text
631
717
  paragraph.pdf_paragraph_composition = self.parse_translate_output(
632
- translate_input, translated_text
718
+ translate_input, translated_text, tracker.last_llm_translate_tracker()
633
719
  )
634
720
  for composition in paragraph.pdf_paragraph_composition:
635
721
  if (
@@ -646,6 +732,7 @@ class ILTranslator:
646
732
  text: str,
647
733
  title_paragraph: PdfParagraph | None = None,
648
734
  local_title_paragraph: PdfParagraph | None = None,
735
+ translate_input: TranslateInput | None = None,
649
736
  ):
650
737
  if self.translation_config.custom_system_prompt:
651
738
  llm_input = [self.translation_config.custom_system_prompt]
@@ -673,6 +760,15 @@ class ILTranslator:
673
760
  llm_input.append(
674
761
  f'Please do not translate formula placeholders like "{self.translate_engine.get_formular_placeholder(3)}"!'
675
762
  )
763
+ if self.translation_config.add_formula_placehold_hint:
764
+ placeholders_hint = translate_input.get_placeholders_hint()
765
+ if placeholders_hint:
766
+ llm_input.append(
767
+ f"This is the formula placeholder hint: \n{placeholders_hint}"
768
+ )
769
+ llm_input.append(
770
+ "The system will automatically replace placeholders with corresponding formulas, please do not translate the placeholders!"
771
+ )
676
772
  prompt_template = f"""
677
773
  ;; Treat next line as plain text input and translate it into {self.translation_config.lang_out}, output translation ONLY. If translation is unnecessary (e.g. proper nouns, codes, {"{{1}}, etc. "}), return the original text. NO explanations. NO notes. Input:\n\n{text}
678
774
 
@@ -704,18 +800,20 @@ class ILTranslator:
704
800
  )
705
801
  if text is None:
706
802
  return
707
-
803
+ llm_translate_tracker = tracker.new_llm_translate_tracker()
708
804
  # Perform translation
709
805
  if self.support_llm_translate:
710
806
  llm_prompt = self.generate_prompt_for_llm(
711
- text, title_paragraph, local_title_paragraph
807
+ text, title_paragraph, local_title_paragraph, translate_input
712
808
  )
809
+ llm_translate_tracker.set_input(llm_prompt)
713
810
  translated_text = self.translate_engine.llm_translate(
714
811
  llm_prompt,
715
812
  rate_limit_params={
716
813
  "paragraph_token_count": paragraph_token_count
717
814
  },
718
815
  )
816
+ llm_translate_tracker.set_output(translated_text)
719
817
  else:
720
818
  translated_text = self.translate_engine.translate(
721
819
  text,
@@ -1,3 +1,4 @@
1
+ import copy
1
2
  import json
2
3
  import logging
3
4
  import re
@@ -11,6 +12,7 @@ from babeldoc.document_il import Document
11
12
  from babeldoc.document_il import Page
12
13
  from babeldoc.document_il import PdfFont
13
14
  from babeldoc.document_il import PdfParagraph
15
+ from babeldoc.document_il.midend import il_translator
14
16
  from babeldoc.document_il.midend.il_translator import DocumentTranslateTracker
15
17
  from babeldoc.document_il.midend.il_translator import ILTranslator
16
18
  from babeldoc.document_il.midend.il_translator import PageTranslateTracker
@@ -94,9 +96,11 @@ class ILTranslatorLLMOnly:
94
96
  # Try to find the first title paragraph
95
97
  title_paragraph = self.find_title_paragraph(docs)
96
98
  self.translation_config.shared_context_cross_split_part.first_paragraph = (
99
+ copy.deepcopy(title_paragraph)
100
+ )
101
+ self.translation_config.shared_context_cross_split_part.recent_title_paragraph = copy.deepcopy(
97
102
  title_paragraph
98
103
  )
99
- self.translation_config.shared_context_cross_split_part.recent_title_paragraph = title_paragraph
100
104
  if title_paragraph:
101
105
  logger.info(f"Found first title paragraph: {title_paragraph.unicode}")
102
106
 
@@ -169,7 +173,9 @@ class ILTranslatorLLMOnly:
169
173
  total_token_count += self.calc_token_count(paragraph.unicode)
170
174
  paragraphs.append(paragraph)
171
175
  if paragraph.layout_label == "title":
172
- self.shared_context_cross_split_part.recent_title_paragraph = paragraph
176
+ self.shared_context_cross_split_part.recent_title_paragraph = (
177
+ copy.deepcopy(paragraph)
178
+ )
173
179
 
174
180
  if total_token_count > 400 or len(paragraphs) > 5:
175
181
  executor.submit(
@@ -217,7 +223,7 @@ class ILTranslatorLLMOnly:
217
223
  should_translate_paragraph = []
218
224
  try:
219
225
  inputs = []
220
-
226
+ llm_translate_trackers = []
221
227
  for i in range(len(batch_paragraph.paragraphs)):
222
228
  paragraph = batch_paragraph.paragraphs[i]
223
229
  tracker = batch_paragraph.trackers[i]
@@ -227,20 +233,31 @@ class ILTranslatorLLMOnly:
227
233
  if text is None:
228
234
  pbar.advance(1)
229
235
  continue
236
+ llm_translate_tracker = tracker.new_llm_translate_tracker()
230
237
  should_translate_paragraph.append(i)
231
- inputs.append((text, translate_input, paragraph, tracker))
238
+ llm_translate_trackers.append(llm_translate_tracker)
239
+ inputs.append(
240
+ (text, translate_input, paragraph, tracker, llm_translate_tracker)
241
+ )
232
242
  if not inputs:
233
243
  return
234
244
  json_format_input = []
235
245
 
236
246
  for id_, input_text in enumerate(inputs):
237
- json_format_input.append(
238
- {
239
- "id": id_,
240
- "input": input_text[0],
241
- "layout_label": input_text[2].layout_label,
242
- }
243
- )
247
+ ti: il_translator.ILTranslator.TranslateInput = input_text[1]
248
+ placeholders_hint = ti.get_placeholders_hint()
249
+ obj = {
250
+ "id": id_,
251
+ "input": input_text[0],
252
+ "layout_label": input_text[2].layout_label,
253
+ }
254
+ if (
255
+ placeholders_hint
256
+ and self.translation_config.add_formula_placehold_hint
257
+ ):
258
+ obj["formula_placeholders_hint"] = placeholders_hint
259
+ json_format_input.append(obj)
260
+
244
261
  json_format_input = json.dumps(
245
262
  json_format_input, ensure_ascii=False, indent=2
246
263
  )
@@ -280,7 +297,12 @@ class ILTranslatorLLMOnly:
280
297
  {
281
298
  "id": 1,
282
299
  "input": "Source",
283
- "layout_label": "plain text"
300
+ "layout_label": "plain text",
301
+ // this is optional
302
+ "formula_placeholders_hint": {
303
+ "placeholder1": "hint1",
304
+ "placeholder2": "hint2"
305
+ }
284
306
  }
285
307
  ```
286
308
  Output:
@@ -299,10 +321,13 @@ class ILTranslatorLLMOnly:
299
321
  llm_input.append(
300
322
  f'Please do not translate style tags like "{self.translate_engine.get_rich_text_left_placeholder(1)}xxx{self.translate_engine.get_rich_text_right_placeholder(2)}"!'
301
323
  )
302
-
303
324
  llm_input.append(
304
325
  f'Please do not translate formula placeholders like "{self.translate_engine.get_formular_placeholder(3)}"!'
305
326
  )
327
+ if self.translation_config.add_formula_placehold_hint:
328
+ llm_input.append(
329
+ "The system will automatically replace placeholders with corresponding formulas, please do not translate the placeholders!"
330
+ )
306
331
  llm_input.append(
307
332
  f"""Here is the input:
308
333
 
@@ -311,10 +336,14 @@ class ILTranslatorLLMOnly:
311
336
  ```"""
312
337
  )
313
338
  final_input = "\n".join(llm_input).strip()
339
+ for llm_translate_tracker in llm_translate_trackers:
340
+ llm_translate_tracker.set_input(final_input)
314
341
  llm_output = self.translate_engine.llm_translate(
315
342
  final_input,
316
343
  rate_limit_params={"paragraph_token_count": paragraph_token_count},
317
344
  )
345
+ for llm_translate_tracker in llm_translate_trackers:
346
+ llm_translate_tracker.set_output(llm_output)
318
347
  llm_output = llm_output.strip()
319
348
 
320
349
  llm_output = self._clean_json_output(llm_output)
@@ -347,6 +376,7 @@ class ILTranslatorLLMOnly:
347
376
 
348
377
  # Get the original input for this translation
349
378
  translate_input = inputs[id_][1]
379
+ llm_translate_tracker = inputs[id_][4]
350
380
 
351
381
  input_unicode = inputs[id_][2].unicode
352
382
  output_unicode = translated_text
@@ -355,6 +385,9 @@ class ILTranslatorLLMOnly:
355
385
  output_token_count = self.calc_token_count(output_unicode)
356
386
 
357
387
  if not (0.3 < output_token_count / input_token_count < 3):
388
+ llm_translate_tracker.set_error_message(
389
+ f"Translation result is too long or too short. Input: {input_token_count}, Output: {output_token_count}"
390
+ )
358
391
  logger.warning(
359
392
  f"Translation result is too long or too short. Input: {input_token_count}, Output: {output_token_count}"
360
393
  )
@@ -362,6 +395,9 @@ class ILTranslatorLLMOnly:
362
395
 
363
396
  edit_distance = Levenshtein.distance(input_unicode, output_unicode)
364
397
  if edit_distance < 5 and input_token_count > 20:
398
+ llm_translate_tracker.set_error_message(
399
+ f"Translation result edit distance is too small. distance: {edit_distance}, input: {input_unicode}, output: {output_unicode}"
400
+ )
365
401
  logger.warning(
366
402
  f"Translation result edit distance is too small. distance: {edit_distance}, input: {input_unicode}, output: {output_unicode}"
367
403
  )
@@ -377,11 +413,15 @@ class ILTranslatorLLMOnly:
377
413
  if pbar:
378
414
  pbar.advance(1)
379
415
  except Exception as e:
380
- logger.exception(f"Error translating paragraph. Error: {e}.")
416
+ error_message = f"Error translating paragraph. Error: {e}."
417
+ logger.exception(error_message)
381
418
  # Ignore error and continue
419
+ for llm_translate_tracker in llm_translate_trackers:
420
+ llm_translate_tracker.set_error_message(error_message)
382
421
  continue
383
422
  finally:
384
423
  if should_fallback:
424
+ inputs[id_][4].set_fallback_to_translate()
385
425
  logger.warning(
386
426
  f"Fallback to simple translation. paragraph id: {inputs[id_][2].debug_id}"
387
427
  )
@@ -402,7 +442,10 @@ class ILTranslatorLLMOnly:
402
442
  )
403
443
 
404
444
  except Exception as e:
405
- logger.warning(f"Error {e} during translation. try fallback")
445
+ error_message = f"Error {e} during translation. try fallback"
446
+ logger.warning(error_message)
447
+ for llm_translate_tracker in llm_translate_trackers:
448
+ llm_translate_tracker.set_error_message(error_message)
406
449
  if not should_translate_paragraph:
407
450
  should_translate_paragraph = list(
408
451
  range(len(batch_paragraph.paragraphs))
@@ -78,7 +78,7 @@ class LayoutParser:
78
78
  # Note: PDF coordinates are from bottom-left,
79
79
  # so we use y2 for top position
80
80
  style = il_version_1.PdfStyle(
81
- font_id="china-ss",
81
+ font_id="base",
82
82
  font_size=4,
83
83
  graphic_state=color,
84
84
  )
@@ -606,6 +606,7 @@ class StylesAndFormulas:
606
606
  r"|.*NimbusSan.*"
607
607
  r"|.*Mincho.*"
608
608
  r"|.*Amerigo.*"
609
+ r"|.*MSGloriolaIIStd.*"
609
610
  r")$"
610
611
  )
611
612
  precise_formula_font_pattern = (
@@ -83,7 +83,7 @@ class TableParser:
83
83
  # Note: PDF coordinates are from bottom-left,
84
84
  # so we use y2 for top position
85
85
  style = il_version_1.PdfStyle(
86
- font_id="china-ss",
86
+ font_id="base",
87
87
  font_size=4,
88
88
  graphic_state=color,
89
89
  )
@@ -277,7 +277,7 @@ class OpenAITranslator(BaseTranslator):
277
277
  logger.exception("Error updating token count")
278
278
 
279
279
  def get_formular_placeholder(self, placeholder_id: int):
280
- return "{{v" + str(placeholder_id) + "}}"
280
+ return "{v" + str(placeholder_id) + "}"
281
281
  return "{{" + str(placeholder_id) + "}}"
282
282
 
283
283
  def get_rich_text_left_placeholder(self, placeholder_id: int):
@@ -1,7 +1,7 @@
1
1
  from babeldoc.document_il import il_version_1
2
2
 
3
3
 
4
- def create_pdf_style(r, g, b, font_id="china-ss", font_size=6):
4
+ def create_pdf_style(r, g, b, font_id="base", font_size=6):
5
5
  """
6
6
  Create a PdfStyle object from RGB values.
7
7
 
@@ -651,8 +651,8 @@ def _do_translate_single(
651
651
 
652
652
  mediabox_data = fix_media_box(doc_pdf2zh)
653
653
 
654
- for page in doc_pdf2zh:
655
- page.insert_font(resfont, None)
654
+ # for page in doc_pdf2zh:
655
+ # page.insert_font(resfont, None)
656
656
 
657
657
  resfont = None
658
658
  doc_pdf2zh.save(temp_pdf_path)
@@ -23,7 +23,7 @@ from babeldoc.translation_config import TranslationConfig
23
23
  from babeldoc.translation_config import WatermarkOutputMode
24
24
 
25
25
  logger = logging.getLogger(__name__)
26
- __version__ = "0.3.39"
26
+ __version__ = "0.3.40"
27
27
 
28
28
 
29
29
  def create_parser():
@@ -70,6 +70,7 @@ class TranslationConfig:
70
70
  skip_scanned_detection: bool = False,
71
71
  ocr_workaround: bool = False,
72
72
  custom_system_prompt: str | None = None,
73
+ add_formula_placehold_hint: bool = False,
73
74
  ):
74
75
  self.translator = translator
75
76
 
@@ -155,6 +156,7 @@ class TranslationConfig:
155
156
  self.table_model = table_model
156
157
  self.show_char_box = show_char_box
157
158
  self.custom_system_prompt = custom_system_prompt
159
+ self.add_formula_placehold_hint = add_formula_placehold_hint
158
160
 
159
161
  def parse_pages(self, pages_str: str | None) -> list[tuple[int, int]] | None:
160
162
  """解析页码字符串,返回页码范围列表
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "BabelDOC"
3
- version = "0.3.39"
3
+ version = "0.3.40"
4
4
  description = "Yet Another Document Translator"
5
5
  license = "AGPL-3.0"
6
6
  readme = "README.md"
@@ -150,7 +150,7 @@ pythonpath = [".", "src"]
150
150
  testpaths = ["tests"]
151
151
 
152
152
  [bumpver]
153
- current_version = "0.3.39"
153
+ current_version = "0.3.40"
154
154
  version_pattern = "MAJOR.MINOR.PATCH[.PYTAGNUM]"
155
155
 
156
156
  [bumpver.file_patterns]
@@ -48,7 +48,7 @@ wheels = [
48
48
 
49
49
  [[package]]
50
50
  name = "babeldoc"
51
- version = "0.3.39"
51
+ version = "0.3.40"
52
52
  source = { editable = "." }
53
53
  dependencies = [
54
54
  { name = "bitstring" },
@@ -1 +0,0 @@
1
- __version__ = "0.3.39"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes