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.
- {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/workflows/publish-to-pypi.yml +2 -2
- {babeldoc-0.3.39 → babeldoc-0.3.40}/PKG-INFO +2 -1
- {babeldoc-0.3.39 → babeldoc-0.3.40}/README.md +1 -0
- babeldoc-0.3.40/babeldoc/__init__.py +1 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/const.py +1 -1
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/add_debug_information.py +1 -1
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/detect_scanned_file.py +1 -1
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/il_translator.py +107 -9
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/il_translator_llm_only.py +58 -15
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/layout_parser.py +1 -1
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/styles_and_formulas.py +1 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/table_parser.py +1 -1
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/translator/translator.py +1 -1
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/utils/style_helper.py +1 -1
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/high_level.py +2 -2
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/main.py +1 -1
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/translation_config.py +2 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/pyproject.toml +2 -2
- {babeldoc-0.3.39 → babeldoc-0.3.40}/uv.lock +1 -1
- babeldoc-0.3.39/babeldoc/__init__.py +0 -1
- {babeldoc-0.3.39 → babeldoc-0.3.40}/.cursorignore +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/ISSUE_TEMPLATE/bug_report.yaml +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/ISSUE_TEMPLATE/feature_request.yaml +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/PULL_REQUEST_TEMPLATE/pr_form.yml +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/dependabot.yml +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/labels.yml +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/release-drafter.yml +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/workflows/codeql.yml +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/workflows/docs.yml +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/workflows/labeler.yml +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/workflows/lint.yml +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/workflows/pr-lint.yml +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/.github/workflows/test.yml +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/.gitignore +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/.pre-commit-config.yaml +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/LICENSE +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/assets/assets.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/assets/embedding_assets_metadata.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/asynchronize/__init__.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/converter.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/__init__.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/babeldoc_exception/BabelDOCException.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/backend/__init__.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/backend/pdf_creater.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/frontend/__init__.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/frontend/il_creater.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/il_version_1.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/il_version_1.rnc +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/il_version_1.rng +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/il_version_1.xsd +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/__init__.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/paragraph_finder.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/remove_descent.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/midend/typesetting.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/translator/__init__.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/translator/cache.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/utils/atomic_integer.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/utils/fontmap.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/utils/layout_helper.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/utils/paragraph_helper.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/utils/priority_thread_pool_executor.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/utils/zstd_helper.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/xml_converter.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/docvision/README.md +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/docvision/__init__.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/docvision/doclayout.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/docvision/rpc_doclayout.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/docvision/table_detection/rapidocr.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/format/office/__init__.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/pdfinterp.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/progress_monitor.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/result_merger.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/split_manager.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/tools/generate_font_metadata.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/tools/italic_assistance.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/tools/italic_recognize_tool.py +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/CODE_OF_CONDUCT.md +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/CONTRIBUTING.md +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/CONTRIBUTOR_REWARD.md +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/ILTranslator/ILTranslator.md +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/PDFCreation/PDFCreation.md +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/PDFParsing/PDFParsing.md +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/README.md +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/StylesAndFormulas/StylesAndFormulas.md +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/Typesetting/Typesetting.md +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/README.md +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/deploy.sh +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-banner.png +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.png +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-big-logo-darkmode-with-transparent-background.svg +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-big-logo-with-transparent-background.png +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-big-logo-with-transparent-background.svg +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-big-logo.png +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-contributor_reward_example.png +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-preview.gif +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-small-logo-with-transparent-background.png +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-small-logo-with-transparent-background.svg +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-small-logo.png +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/index.md +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/intro-to-pdf-object.md +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/requirements.txt +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/docs/supported_languages.md +0 -0
- {babeldoc-0.3.39 → babeldoc-0.3.40}/mkdocs.yml +0 -0
- {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.
|
|
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"
|
|
@@ -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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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))
|
|
@@ -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 "{
|
|
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):
|
|
@@ -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
|
-
|
|
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)
|
|
@@ -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.
|
|
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.
|
|
153
|
+
current_version = "0.3.40"
|
|
154
154
|
version_pattern = "MAJOR.MINOR.PATCH[.PYTAGNUM]"
|
|
155
155
|
|
|
156
156
|
[bumpver.file_patterns]
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/babeldoc_exception/BabelDOCException.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.3.39 → babeldoc-0.3.40}/babeldoc/document_il/utils/priority_thread_pool_executor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/AsyncTranslate/AsyncTranslate.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.3.39 → babeldoc-0.3.40}/docs/ImplementationDetails/ParagraphFinding/ParagraphFinding.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-big-logo-with-transparent-background.png
RENAMED
|
File without changes
|
{babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-big-logo-with-transparent-background.svg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-small-logo-with-transparent-background.png
RENAMED
|
File without changes
|
{babeldoc-0.3.39 → babeldoc-0.3.40}/docs/images/babeldoc-small-logo-with-transparent-background.svg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|