BabelDOC 0.3.39__tar.gz → 0.3.41__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.41}/.github/workflows/publish-to-pypi.yml +2 -2
  2. {babeldoc-0.3.39 → babeldoc-0.3.41}/PKG-INFO +2 -1
  3. {babeldoc-0.3.39 → babeldoc-0.3.41}/README.md +1 -0
  4. babeldoc-0.3.41/babeldoc/__init__.py +1 -0
  5. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/const.py +1 -1
  6. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/midend/add_debug_information.py +1 -1
  7. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/midend/detect_scanned_file.py +1 -1
  8. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/midend/il_translator.py +181 -26
  9. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/midend/il_translator_llm_only.py +97 -27
  10. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/midend/layout_parser.py +1 -1
  11. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/midend/styles_and_formulas.py +1 -0
  12. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/midend/table_parser.py +1 -1
  13. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/translator/translator.py +6 -3
  14. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/utils/style_helper.py +1 -1
  15. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/high_level.py +2 -2
  16. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/main.py +1 -1
  17. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/translation_config.py +2 -0
  18. {babeldoc-0.3.39 → babeldoc-0.3.41}/pyproject.toml +2 -2
  19. {babeldoc-0.3.39 → babeldoc-0.3.41}/uv.lock +1 -1
  20. babeldoc-0.3.39/babeldoc/__init__.py +0 -1
  21. {babeldoc-0.3.39 → babeldoc-0.3.41}/.cursorignore +0 -0
  22. {babeldoc-0.3.39 → babeldoc-0.3.41}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
  23. {babeldoc-0.3.39 → babeldoc-0.3.41}/.github/ISSUE_TEMPLATE/feature_request.yaml +0 -0
  24. {babeldoc-0.3.39 → babeldoc-0.3.41}/.github/PULL_REQUEST_TEMPLATE/pr_form.yml +0 -0
  25. {babeldoc-0.3.39 → babeldoc-0.3.41}/.github/dependabot.yml +0 -0
  26. {babeldoc-0.3.39 → babeldoc-0.3.41}/.github/labels.yml +0 -0
  27. {babeldoc-0.3.39 → babeldoc-0.3.41}/.github/release-drafter.yml +0 -0
  28. {babeldoc-0.3.39 → babeldoc-0.3.41}/.github/workflows/codeql.yml +0 -0
  29. {babeldoc-0.3.39 → babeldoc-0.3.41}/.github/workflows/docs.yml +0 -0
  30. {babeldoc-0.3.39 → babeldoc-0.3.41}/.github/workflows/labeler.yml +0 -0
  31. {babeldoc-0.3.39 → babeldoc-0.3.41}/.github/workflows/lint.yml +0 -0
  32. {babeldoc-0.3.39 → babeldoc-0.3.41}/.github/workflows/pr-lint.yml +0 -0
  33. {babeldoc-0.3.39 → babeldoc-0.3.41}/.github/workflows/test.yml +0 -0
  34. {babeldoc-0.3.39 → babeldoc-0.3.41}/.gitignore +0 -0
  35. {babeldoc-0.3.39 → babeldoc-0.3.41}/.pre-commit-config.yaml +0 -0
  36. {babeldoc-0.3.39 → babeldoc-0.3.41}/LICENSE +0 -0
  37. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/assets/assets.py +0 -0
  38. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/assets/embedding_assets_metadata.py +0 -0
  39. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/asynchronize/__init__.py +0 -0
  40. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/converter.py +0 -0
  41. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/__init__.py +0 -0
  42. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/babeldoc_exception/BabelDOCException.py +0 -0
  43. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/backend/__init__.py +0 -0
  44. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/backend/pdf_creater.py +0 -0
  45. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/frontend/__init__.py +0 -0
  46. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/frontend/il_creater.py +0 -0
  47. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/il_version_1.py +0 -0
  48. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/il_version_1.rnc +0 -0
  49. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/il_version_1.rng +0 -0
  50. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/il_version_1.xsd +0 -0
  51. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/midend/__init__.py +0 -0
  52. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/midend/paragraph_finder.py +0 -0
  53. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/midend/remove_descent.py +0 -0
  54. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/midend/typesetting.py +0 -0
  55. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/translator/__init__.py +0 -0
  56. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/translator/cache.py +0 -0
  57. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/utils/atomic_integer.py +0 -0
  58. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/utils/fontmap.py +0 -0
  59. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/utils/layout_helper.py +0 -0
  60. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/utils/paragraph_helper.py +0 -0
  61. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/utils/priority_thread_pool_executor.py +0 -0
  62. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/utils/zstd_helper.py +0 -0
  63. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/document_il/xml_converter.py +0 -0
  64. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/docvision/README.md +0 -0
  65. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/docvision/__init__.py +0 -0
  66. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/docvision/doclayout.py +0 -0
  67. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/docvision/rpc_doclayout.py +0 -0
  68. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/docvision/table_detection/rapidocr.py +0 -0
  69. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/format/office/__init__.py +0 -0
  70. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/pdfinterp.py +0 -0
  71. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/progress_monitor.py +0 -0
  72. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/result_merger.py +0 -0
  73. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/split_manager.py +0 -0
  74. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/tools/generate_font_metadata.py +0 -0
  75. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/tools/italic_assistance.py +0 -0
  76. {babeldoc-0.3.39 → babeldoc-0.3.41}/babeldoc/tools/italic_recognize_tool.py +0 -0
  77. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/CODE_OF_CONDUCT.md +0 -0
  78. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/CONTRIBUTING.md +0 -0
  79. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/CONTRIBUTOR_REWARD.md +0 -0
  80. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md +0 -0
  81. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/ImplementationDetails/ILTranslator/ILTranslator.md +0 -0
  82. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/ImplementationDetails/PDFCreation/PDFCreation.md +0 -0
  83. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/ImplementationDetails/PDFParsing/PDFParsing.md +0 -0
  84. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md +0 -0
  85. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/ImplementationDetails/README.md +0 -0
  86. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/ImplementationDetails/StylesAndFormulas/StylesAndFormulas.md +0 -0
  87. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/ImplementationDetails/Typesetting/Typesetting.md +0 -0
  88. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/README.md +0 -0
  89. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/deploy.sh +0 -0
  90. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/images/babeldoc-banner.png +0 -0
  91. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.png +0 -0
  92. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.svg +0 -0
  93. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/images/babeldoc-big-logo-with-transparent-background.png +0 -0
  94. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/images/babeldoc-big-logo-with-transparent-background.svg +0 -0
  95. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/images/babeldoc-big-logo.png +0 -0
  96. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/images/babeldoc-contributor_reward_example.png +0 -0
  97. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/images/babeldoc-preview.gif +0 -0
  98. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/images/babeldoc-small-logo-with-transparent-background.png +0 -0
  99. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/images/babeldoc-small-logo-with-transparent-background.svg +0 -0
  100. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/images/babeldoc-small-logo.png +0 -0
  101. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/index.md +0 -0
  102. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/intro-to-pdf-object.md +0 -0
  103. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/requirements.txt +0 -0
  104. {babeldoc-0.3.39 → babeldoc-0.3.41}/docs/supported_languages.md +0 -0
  105. {babeldoc-0.3.39 → babeldoc-0.3.41}/mkdocs.yml +0 -0
  106. {babeldoc-0.3.39 → babeldoc-0.3.41}/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.41
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.41"
@@ -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.41"
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
@@ -36,18 +39,29 @@ class RichTextPlaceholder:
36
39
  composition: PdfSameStyleCharacters,
37
40
  left_placeholder: str,
38
41
  right_placeholder: str,
42
+ left_regex_pattern: str = None,
43
+ right_regex_pattern: str = None,
39
44
  ):
40
45
  self.id = placeholder_id
41
46
  self.composition = composition
42
47
  self.left_placeholder = left_placeholder
43
48
  self.right_placeholder = right_placeholder
49
+ self.left_regex_pattern = left_regex_pattern
50
+ self.right_regex_pattern = right_regex_pattern
44
51
 
45
52
 
46
53
  class FormulaPlaceholder:
47
- def __init__(self, placeholder_id: int, formula: PdfFormula, placeholder: str):
54
+ def __init__(
55
+ self,
56
+ placeholder_id: int,
57
+ formula: PdfFormula,
58
+ placeholder: str,
59
+ regex_pattern: str,
60
+ ):
48
61
  self.id = placeholder_id
49
62
  self.formula = formula
50
63
  self.placeholder = placeholder
64
+ self.regex_pattern = regex_pattern
51
65
 
52
66
 
53
67
  class PbarContext:
@@ -78,6 +92,10 @@ class DocumentTranslateTracker:
78
92
  i_str = getattr(para, "input", None)
79
93
  o_str = getattr(para, "output", None)
80
94
  pdf_unicode = getattr(para, "pdf_unicode", None)
95
+ llm_translate_trackers = getattr(para, "llm_translate_trackers", None)
96
+ llm_translate_trackers_json = []
97
+ for tracker in llm_translate_trackers:
98
+ llm_translate_trackers_json.append(tracker.to_dict())
81
99
  if pdf_unicode is None or i_str is None:
82
100
  continue
83
101
  paragraphs.append(
@@ -85,6 +103,7 @@ class DocumentTranslateTracker:
85
103
  "input": i_str,
86
104
  "output": o_str,
87
105
  "pdf_unicode": pdf_unicode,
106
+ "llm_translate_trackers": llm_translate_trackers_json,
88
107
  },
89
108
  )
90
109
  pages.append({"paragraph": paragraphs})
@@ -103,7 +122,7 @@ class PageTranslateTracker:
103
122
 
104
123
  class ParagraphTranslateTracker:
105
124
  def __init__(self):
106
- pass
125
+ self.llm_translate_trackers = []
107
126
 
108
127
  def set_pdf_unicode(self, unicode: str):
109
128
  self.pdf_unicode = unicode
@@ -114,6 +133,52 @@ class ParagraphTranslateTracker:
114
133
  def set_output(self, output: str):
115
134
  self.output = output
116
135
 
136
+ def new_llm_translate_tracker(self) -> LLMTranslateTracker:
137
+ tracker = LLMTranslateTracker()
138
+ self.llm_translate_trackers.append(tracker)
139
+ return tracker
140
+
141
+ def last_llm_translate_tracker(self) -> LLMTranslateTracker | None:
142
+ if self.llm_translate_trackers:
143
+ return self.llm_translate_trackers[-1]
144
+ return None
145
+
146
+
147
+ class LLMTranslateTracker:
148
+ def __init__(self):
149
+ self.input = ""
150
+ self.output = ""
151
+ self.has_error = False
152
+ self.error_message = ""
153
+ self.placeholder_full_match = False
154
+ self.fallback_to_translate = False
155
+
156
+ def set_input(self, input_text: str):
157
+ self.input = input_text
158
+
159
+ def set_output(self, output_text: str):
160
+ self.output = output_text
161
+
162
+ def set_error_message(self, error_message: str):
163
+ self.has_error = True
164
+ self.error_message = error_message
165
+
166
+ def set_placeholder_full_match(self):
167
+ self.placeholder_full_match = True
168
+
169
+ def set_fallback_to_translate(self):
170
+ self.fallback_to_translate = True
171
+
172
+ def to_dict(self):
173
+ return {
174
+ "input": self.input,
175
+ "output": self.output,
176
+ "has_error": self.has_error,
177
+ "error_message": self.error_message,
178
+ "placeholder_full_match": self.placeholder_full_match,
179
+ "fallback_to_translate": self.fallback_to_translate,
180
+ }
181
+
117
182
 
118
183
  class ILTranslator:
119
184
  stage_name = "Translate Paragraphs"
@@ -156,9 +221,11 @@ class ILTranslator:
156
221
  # Try to find the first title paragraph
157
222
  title_paragraph = self.find_title_paragraph(docs)
158
223
  self.translation_config.shared_context_cross_split_part.first_paragraph = (
224
+ copy.deepcopy(title_paragraph)
225
+ )
226
+ self.translation_config.shared_context_cross_split_part.recent_title_paragraph = copy.deepcopy(
159
227
  title_paragraph
160
228
  )
161
- self.translation_config.shared_context_cross_split_part.recent_title_paragraph = title_paragraph
162
229
  if title_paragraph:
163
230
  logger.info(f"Found first title paragraph: {title_paragraph.unicode}")
164
231
 
@@ -220,7 +287,9 @@ class ILTranslator:
220
287
  # self.translate_paragraph(paragraph, pbar,tracker.new_paragraph(), page_font_map, page_xobj_font_map)
221
288
  paragraph_token_count = self.calc_token_count(paragraph.unicode)
222
289
  if paragraph.layout_label == "title":
223
- self.shared_context_cross_split_part.recent_title_paragraph = paragraph
290
+ self.shared_context_cross_split_part.recent_title_paragraph = (
291
+ copy.deepcopy(paragraph)
292
+ )
224
293
  executor.submit(
225
294
  self.translate_paragraph,
226
295
  paragraph,
@@ -245,6 +314,24 @@ class ILTranslator:
245
314
  self.placeholders = placeholders
246
315
  self.base_style = base_style
247
316
 
317
+ def get_placeholders_hint(self) -> dict[str, str] | None:
318
+ hint = {}
319
+ for placeholder in self.placeholders:
320
+ if isinstance(placeholder, FormulaPlaceholder):
321
+ cid_count = 0
322
+ for char in placeholder.formula.pdf_character:
323
+ if re.match(r"^\(cid:\d+\)$", char.char_unicode):
324
+ cid_count += 1
325
+ if cid_count > len(placeholder.formula.pdf_character) * 0.8:
326
+ continue
327
+
328
+ hint[placeholder.placeholder] = get_char_unicode_string(
329
+ placeholder.formula.pdf_character
330
+ )
331
+ if hint:
332
+ return hint
333
+ return None
334
+
248
335
  def create_formula_placeholder(
249
336
  self,
250
337
  formula: PdfFormula,
@@ -252,10 +339,14 @@ class ILTranslator:
252
339
  paragraph: PdfParagraph,
253
340
  ):
254
341
  placeholder = self.translate_engine.get_formular_placeholder(formula_id)
342
+ if isinstance(placeholder, tuple):
343
+ placeholder, regex_pattern = placeholder
344
+ else:
345
+ regex_pattern = re.escape(placeholder)
255
346
  if placeholder in paragraph.unicode:
256
347
  return self.create_formula_placeholder(formula, formula_id + 1, paragraph)
257
348
 
258
- return FormulaPlaceholder(formula_id, formula, placeholder)
349
+ return FormulaPlaceholder(formula_id, formula, placeholder, regex_pattern)
259
350
 
260
351
  def create_rich_text_placeholder(
261
352
  self,
@@ -269,6 +360,14 @@ class ILTranslator:
269
360
  right_placeholder = self.translate_engine.get_rich_text_right_placeholder(
270
361
  composition_id,
271
362
  )
363
+ if isinstance(left_placeholder, tuple):
364
+ left_placeholder, left_placeholder_regex_pattern = left_placeholder
365
+ else:
366
+ left_placeholder_regex_pattern = re.escape(left_placeholder)
367
+ if isinstance(right_placeholder, tuple):
368
+ right_placeholder, right_placeholder_regex_pattern = right_placeholder
369
+ else:
370
+ right_placeholder_regex_pattern = re.escape(right_placeholder)
272
371
  if (
273
372
  left_placeholder in paragraph.unicode
274
373
  or right_placeholder in paragraph.unicode
@@ -284,6 +383,8 @@ class ILTranslator:
284
383
  composition,
285
384
  left_placeholder,
286
385
  right_placeholder,
386
+ left_placeholder_regex_pattern,
387
+ right_placeholder_regex_pattern,
287
388
  )
288
389
 
289
390
  def get_translate_input(
@@ -452,9 +553,8 @@ class ILTranslator:
452
553
  self,
453
554
  input_text: TranslateInput,
454
555
  output: str,
556
+ llm_translate_tracker: LLMTranslateTracker | None = None,
455
557
  ) -> [PdfParagraphComposition]:
456
- import re
457
-
458
558
  result = []
459
559
 
460
560
  # 如果没有占位符,直接返回整个文本
@@ -463,6 +563,8 @@ class ILTranslator:
463
563
  comp.pdf_same_style_unicode_characters = PdfSameStyleUnicodeCharacters()
464
564
  comp.pdf_same_style_unicode_characters.unicode = output
465
565
  comp.pdf_same_style_unicode_characters.pdf_style = input_text.base_style
566
+ if llm_translate_tracker:
567
+ llm_translate_tracker.set_placeholder_full_match()
466
568
  return [comp]
467
569
 
468
570
  # 构建正则表达式模式
@@ -473,18 +575,28 @@ class ILTranslator:
473
575
  for placeholder in input_text.placeholders:
474
576
  if isinstance(placeholder, FormulaPlaceholder):
475
577
  # 转义特殊字符
476
- pattern = re.escape(placeholder.placeholder)
578
+ # pattern = re.escape(placeholder.placeholder)
579
+ pattern = placeholder.regex_pattern
477
580
  patterns.append(f"({pattern})")
478
581
  placeholder_patterns.append(f"({pattern})")
479
582
  placeholder_map[placeholder.placeholder] = placeholder
480
583
  else:
481
- left = re.escape(placeholder.left_placeholder)
482
- right = re.escape(placeholder.right_placeholder)
584
+ left = placeholder.left_regex_pattern
585
+ right = placeholder.right_regex_pattern
483
586
  patterns.append(f"({left}.*?{right})")
484
587
  placeholder_patterns.append(f"({left})")
485
588
  placeholder_patterns.append(f"({right})")
486
589
  placeholder_map[placeholder.left_placeholder] = placeholder
487
-
590
+ all_match = True
591
+ for pattern in patterns:
592
+ if not re.search(pattern, output, flags=re.IGNORECASE):
593
+ all_match = False
594
+ break
595
+ if all_match:
596
+ if llm_translate_tracker:
597
+ llm_translate_tracker.set_placeholder_full_match()
598
+ else:
599
+ logger.debug(f"Failed to match all placeholder for {input_text.unicode}")
488
600
  # 合并所有模式
489
601
  combined_pattern = "|".join(patterns)
490
602
  combined_placeholder_pattern = "|".join(placeholder_patterns)
@@ -534,13 +646,12 @@ class ILTranslator:
534
646
  p
535
647
  for p in input_text.placeholders
536
648
  if not isinstance(p, FormulaPlaceholder)
537
- and matched_text.startswith(p.left_placeholder)
649
+ and re.match(f"^{p.left_regex_pattern}", matched_text)
538
650
  )
539
- text = matched_text[
540
- len(placeholder.left_placeholder) : -len(
541
- placeholder.right_placeholder,
542
- )
543
- ]
651
+ text = re.match(
652
+ f"^{placeholder.left_regex_pattern}(.+){placeholder.right_regex_pattern}$",
653
+ matched_text,
654
+ ).group(1)
544
655
 
545
656
  if isinstance(
546
657
  placeholder.composition,
@@ -626,10 +737,12 @@ class ILTranslator:
626
737
  """Post-translation processing: update paragraph with translated text."""
627
738
  tracker.set_output(translated_text)
628
739
  if translated_text == translate_input.unicode:
740
+ if llm_translate_tracker := tracker.last_llm_translate_tracker():
741
+ llm_translate_tracker.set_placeholder_full_match()
629
742
  return False
630
743
  paragraph.unicode = translated_text
631
744
  paragraph.pdf_paragraph_composition = self.parse_translate_output(
632
- translate_input, translated_text
745
+ translate_input, translated_text, tracker.last_llm_translate_tracker()
633
746
  )
634
747
  for composition in paragraph.pdf_paragraph_composition:
635
748
  if (
@@ -646,35 +759,75 @@ class ILTranslator:
646
759
  text: str,
647
760
  title_paragraph: PdfParagraph | None = None,
648
761
  local_title_paragraph: PdfParagraph | None = None,
762
+ translate_input: TranslateInput | None = None,
649
763
  ):
650
764
  if self.translation_config.custom_system_prompt:
651
765
  llm_input = [self.translation_config.custom_system_prompt]
652
766
  else:
653
767
  llm_input = [
654
- "You are a professional, authentic machine translation engine."
768
+ f"You are a professional and reliable machine translation engine responsible for translating the input text into {self.translation_config.lang_out}."
655
769
  ]
770
+ llm_hint = []
656
771
 
657
772
  if title_paragraph:
658
- llm_input.append(
773
+ llm_hint.append(
659
774
  f"The first title in the full text: {title_paragraph.unicode}"
660
775
  )
661
776
  if (
662
777
  local_title_paragraph
663
778
  and local_title_paragraph.debug_id != title_paragraph.debug_id
664
779
  ):
665
- llm_input.append(
780
+ llm_hint.append(
666
781
  f"The most similar title in the full text: {local_title_paragraph.unicode}"
667
782
  )
783
+
784
+ if self.translation_config.add_formula_placehold_hint:
785
+ placeholders_hint = translate_input.get_placeholders_hint()
786
+ if placeholders_hint:
787
+ llm_hint.append(
788
+ f"This is the formula placeholder hint: \n{placeholders_hint}"
789
+ )
790
+
791
+ if llm_hint:
792
+ llm_input.append(
793
+ "When translating, please refer to the following information to improve translation quality:"
794
+ )
795
+ for i, line in enumerate(llm_hint):
796
+ llm_input.append(f"{i}. {line}")
797
+ llm_input.append("When translating, please follow the following rules:")
798
+
799
+ rich_text_left_placeholder = (
800
+ self.translate_engine.get_rich_text_left_placeholder(1)
801
+ )
802
+ if isinstance(rich_text_left_placeholder, tuple):
803
+ rich_text_left_placeholder = rich_text_left_placeholder[0]
804
+ rich_text_right_placeholder = (
805
+ self.translate_engine.get_rich_text_right_placeholder(2)
806
+ )
807
+ if isinstance(rich_text_right_placeholder, tuple):
808
+ rich_text_right_placeholder = rich_text_right_placeholder[0]
809
+
668
810
  # Create a structured prompt template for LLM translation
669
811
  llm_input.append(
670
- 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)}"!'
812
+ f'1. Do not translate style tags, such as "{rich_text_left_placeholder}xxx{rich_text_right_placeholder}"!'
671
813
  )
672
814
 
815
+ formula_placeholder = self.translate_engine.get_formular_placeholder(3)
816
+ if isinstance(formula_placeholder, tuple):
817
+ formula_placeholder = formula_placeholder[0]
818
+
819
+ llm_input.append(
820
+ f'2. Do not translate formula placeholders, such as "{formula_placeholder}". The system will automatically replace the placeholders with the corresponding formulas.'
821
+ )
673
822
  llm_input.append(
674
- f'Please do not translate formula placeholders like "{self.translate_engine.get_formular_placeholder(3)}"!'
823
+ "3. If there is no need to translate (such as proper nouns, codes, etc.), then return the original text."
675
824
  )
825
+ llm_input.append(
826
+ "4. Only output the translation result without explanations and annotations."
827
+ )
828
+ llm_input.append(f"5. Translate text into {self.translation_config.lang_out}.")
676
829
  prompt_template = f"""
677
- ;; 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}
830
+ Now, please carefully read the following text to be translated and directly output your translation.\n\n{text}
678
831
 
679
832
  """
680
833
  llm_input.append(prompt_template)
@@ -704,18 +857,20 @@ class ILTranslator:
704
857
  )
705
858
  if text is None:
706
859
  return
707
-
860
+ llm_translate_tracker = tracker.new_llm_translate_tracker()
708
861
  # Perform translation
709
862
  if self.support_llm_translate:
710
863
  llm_prompt = self.generate_prompt_for_llm(
711
- text, title_paragraph, local_title_paragraph
864
+ text, title_paragraph, local_title_paragraph, translate_input
712
865
  )
866
+ llm_translate_tracker.set_input(llm_prompt)
713
867
  translated_text = self.translate_engine.llm_translate(
714
868
  llm_prompt,
715
869
  rate_limit_params={
716
870
  "paragraph_token_count": paragraph_token_count
717
871
  },
718
872
  )
873
+ llm_translate_tracker.set_output(translated_text)
719
874
  else:
720
875
  translated_text = self.translate_engine.translate(
721
876
  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,9 +173,11 @@ 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
- if total_token_count > 400 or len(paragraphs) > 5:
180
+ if total_token_count > 200 or len(paragraphs) > 10:
175
181
  executor.submit(
176
182
  self.translate_paragraph,
177
183
  BatchParagraph(paragraphs, tracker),
@@ -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
  )
@@ -248,24 +265,57 @@ class ILTranslatorLLMOnly:
248
265
  llm_input = [self.translation_config.custom_system_prompt]
249
266
  else:
250
267
  llm_input = [
251
- "You are a professional, authentic machine translation engine."
268
+ f"You are a professional and reliable machine translation engine responsible for translating the input text into {self.translation_config.lang_out}."
252
269
  ]
253
270
 
271
+ llm_hint = []
254
272
  if title_paragraph:
255
- llm_input.append(
273
+ llm_hint.append(
256
274
  f"The first title in the full text: {title_paragraph.unicode}"
257
275
  )
258
276
  if (
259
277
  local_title_paragraph
260
278
  and local_title_paragraph.debug_id != title_paragraph.debug_id
261
279
  ):
262
- llm_input.append(
280
+ llm_hint.append(
263
281
  f"The most similar title in the full text: {local_title_paragraph.unicode}"
264
282
  )
283
+
284
+ if llm_hint:
285
+ llm_input.append(
286
+ "When translating, please refer to the following information to improve translation quality:"
287
+ )
288
+ for i, line in enumerate(llm_hint):
289
+ llm_input.append(f"{i}. {line}")
290
+ llm_input.append("When translating, please follow the following rules:")
291
+ # Create a structured prompt template for LLM translation
292
+ rich_text_left_placeholder = (
293
+ self.translate_engine.get_rich_text_left_placeholder(1)
294
+ )
295
+ if isinstance(rich_text_left_placeholder, tuple):
296
+ rich_text_left_placeholder = rich_text_left_placeholder[0]
297
+ rich_text_right_placeholder = (
298
+ self.translate_engine.get_rich_text_right_placeholder(2)
299
+ )
300
+ if isinstance(rich_text_right_placeholder, tuple):
301
+ rich_text_right_placeholder = rich_text_right_placeholder[0]
302
+ llm_input.append(
303
+ f'1. Do not translate style tags, such as "{rich_text_left_placeholder}xxx{rich_text_right_placeholder}"!'
304
+ )
305
+
306
+ formula_placeholder = self.translate_engine.get_formular_placeholder(3)
307
+ if isinstance(formula_placeholder, tuple):
308
+ formula_placeholder = formula_placeholder[0]
309
+ llm_input.append(
310
+ f'2. Do not translate formula placeholders, such as "{formula_placeholder}". The system will automatically replace the placeholders with the corresponding formulas.'
311
+ )
312
+ llm_input.append(
313
+ "3. If there is no need to translate (such as proper nouns, codes, etc.), then return the original text."
314
+ )
265
315
  # Create a structured prompt template for LLM translation
266
316
  prompt_template = (
267
317
  f"""
268
- You will be given a JSON formatted input containing entries with "id" and "input" fields.
318
+ 4. You will be given a JSON formatted input containing entries with "id" and "input" fields.
269
319
 
270
320
  For each entry in the JSON, translate the contents of the "input" field into {self.translation_config.lang_out}.
271
321
  Write the translation back into the "output" field for that entry.
@@ -280,7 +330,12 @@ class ILTranslatorLLMOnly:
280
330
  {
281
331
  "id": 1,
282
332
  "input": "Source",
283
- "layout_label": "plain text"
333
+ "layout_label": "plain text",
334
+ // this is optional
335
+ "formula_placeholders_hint": {
336
+ "placeholder1": "hint1",
337
+ "placeholder2": "hint2"
338
+ }
284
339
  }
285
340
  ```
286
341
  Output:
@@ -296,13 +351,6 @@ class ILTranslatorLLMOnly:
296
351
  """
297
352
  )
298
353
  llm_input.append(prompt_template)
299
- llm_input.append(
300
- 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
- )
302
-
303
- llm_input.append(
304
- f'Please do not translate formula placeholders like "{self.translate_engine.get_formular_placeholder(3)}"!'
305
- )
306
354
  llm_input.append(
307
355
  f"""Here is the input:
308
356
 
@@ -311,17 +359,24 @@ class ILTranslatorLLMOnly:
311
359
  ```"""
312
360
  )
313
361
  final_input = "\n".join(llm_input).strip()
362
+ for llm_translate_tracker in llm_translate_trackers:
363
+ llm_translate_tracker.set_input(final_input)
314
364
  llm_output = self.translate_engine.llm_translate(
315
365
  final_input,
316
366
  rate_limit_params={"paragraph_token_count": paragraph_token_count},
317
367
  )
368
+ for llm_translate_tracker in llm_translate_trackers:
369
+ llm_translate_tracker.set_output(llm_output)
318
370
  llm_output = llm_output.strip()
319
371
 
320
372
  llm_output = self._clean_json_output(llm_output)
321
373
 
322
374
  parsed_output = json.loads(llm_output)
323
375
 
324
- translation_results = {item["id"]: item["output"] for item in parsed_output}
376
+ translation_results = {
377
+ item["id"]: item.get("output", item.get("input"))
378
+ for item in parsed_output
379
+ }
325
380
 
326
381
  if len(translation_results) != len(inputs):
327
382
  raise Exception(
@@ -347,6 +402,7 @@ class ILTranslatorLLMOnly:
347
402
 
348
403
  # Get the original input for this translation
349
404
  translate_input = inputs[id_][1]
405
+ llm_translate_tracker = inputs[id_][4]
350
406
 
351
407
  input_unicode = inputs[id_][2].unicode
352
408
  output_unicode = translated_text
@@ -355,6 +411,9 @@ class ILTranslatorLLMOnly:
355
411
  output_token_count = self.calc_token_count(output_unicode)
356
412
 
357
413
  if not (0.3 < output_token_count / input_token_count < 3):
414
+ llm_translate_tracker.set_error_message(
415
+ f"Translation result is too long or too short. Input: {input_token_count}, Output: {output_token_count}"
416
+ )
358
417
  logger.warning(
359
418
  f"Translation result is too long or too short. Input: {input_token_count}, Output: {output_token_count}"
360
419
  )
@@ -362,6 +421,9 @@ class ILTranslatorLLMOnly:
362
421
 
363
422
  edit_distance = Levenshtein.distance(input_unicode, output_unicode)
364
423
  if edit_distance < 5 and input_token_count > 20:
424
+ llm_translate_tracker.set_error_message(
425
+ f"Translation result edit distance is too small. distance: {edit_distance}, input: {input_unicode}, output: {output_unicode}"
426
+ )
365
427
  logger.warning(
366
428
  f"Translation result edit distance is too small. distance: {edit_distance}, input: {input_unicode}, output: {output_unicode}"
367
429
  )
@@ -377,11 +439,15 @@ class ILTranslatorLLMOnly:
377
439
  if pbar:
378
440
  pbar.advance(1)
379
441
  except Exception as e:
380
- logger.exception(f"Error translating paragraph. Error: {e}.")
442
+ error_message = f"Error translating paragraph. Error: {e}."
443
+ logger.exception(error_message)
381
444
  # Ignore error and continue
445
+ for llm_translate_tracker in llm_translate_trackers:
446
+ llm_translate_tracker.set_error_message(error_message)
382
447
  continue
383
448
  finally:
384
449
  if should_fallback:
450
+ inputs[id_][4].set_fallback_to_translate()
385
451
  logger.warning(
386
452
  f"Fallback to simple translation. paragraph id: {inputs[id_][2].debug_id}"
387
453
  )
@@ -402,7 +468,11 @@ class ILTranslatorLLMOnly:
402
468
  )
403
469
 
404
470
  except Exception as e:
405
- logger.warning(f"Error {e} during translation. try fallback")
471
+ error_message = f"Error {e} during translation. try fallback"
472
+ logger.warning(error_message)
473
+ for llm_translate_tracker in llm_translate_trackers:
474
+ llm_translate_tracker.set_error_message(error_message)
475
+ llm_translate_tracker.set_fallback_to_translate()
406
476
  if not should_translate_paragraph:
407
477
  should_translate_paragraph = list(
408
478
  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,11 +277,14 @@ 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) + "}", f"{{\\s*v\\s*{placeholder_id}\\s*}}"
281
281
  return "{{" + str(placeholder_id) + "}}"
282
282
 
283
283
  def get_rich_text_left_placeholder(self, placeholder_id: int):
284
- return f"<style id='{placeholder_id}'>"
284
+ return (
285
+ f"<style id='{placeholder_id}'>",
286
+ f"<\\s*style\\s*id\\s*=\\s*'\\s*{placeholder_id}\\s*'\\s*>",
287
+ )
285
288
 
286
289
  def get_rich_text_right_placeholder(self, placeholder_id: int):
287
- return "</style>"
290
+ return "</style>", r"<\s*\/\s*style\s*>"
@@ -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.41"
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.41"
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.41"
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.41"
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