auto-i18n-lib 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 BONA
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: auto-i18n-lib
3
+ Version: 0.1.0
4
+ Summary: Dynamic HTML translation with OpenAI
5
+ Author-email: Your Name <you@example.com>
6
+ License: MIT
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Dynamic: license-file
11
+
12
+ # autoi18n
13
+
14
+ Runtime HTML i18n with OpenAI: translate the page on first request, cache results to JSON, reuse on subsequent requests, and auto-sync when the source changes.
15
+
16
+ **PyPI name:** `autoi18n` | **Import:** `autoi18n`
17
+
18
+ ---
19
+
20
+ ## Features
21
+
22
+ - 🔁 On-the-fly translation of raw HTML strings
23
+ - 🗂️ JSON cache per target language (on disk)
24
+ - 🔄 Auto-sync: stale entries removed when source changes
25
+ - 🧩 Chunking for model context limits (long pages split safely)
26
+ - 🛡️ Preserves `<script>`/`<style>` as-is
27
+ - 🎛️ Concise UI translation (e.g., buttons)
28
+ - 🌐 Helpers: detect browser lang, pick alternate lang
29
+
30
+ ---
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install autoi18n
36
+ ```
37
+ ## Quick start
38
+ ```
39
+ from autoi18n import Translator
40
+
41
+ html_in = "<h1>Добро пожаловать</h1><p>Это тест.</p>"
42
+ tr = Translator(
43
+ source_lang="ru",
44
+ cache_dir="./translations", # JSON cache folder
45
+ # api_key="sk-..." # or use OPENAI_API_KEY env var
46
+ )
47
+
48
+ html_out = tr.translate_html(html_in, target_lang="en", page_name="page")
49
+ print(html_out)
50
+ First call translates via OpenAI and writes ./translations/page.en.json.
51
+ Next calls reuse the cache and only translate new/changed strings.
52
+ ```
53
+ ## Environment
54
+ ```
55
+ OPENAI_API_KEY — used if api_key not passed to Translator.
56
+ ```
57
+ ## API
58
+
59
+ ```
60
+ Translator(source_lang="ru", cache_dir="./translations", api_key=None)
61
+ translate_html(html: str, target_lang: str, page_name: str = "page") -> str
62
+ ```
63
+
64
+ ## Translates visible text nodes, preserves script/style, chunks long text, updates cache JSON.
65
+
66
+ ```
67
+ translate_text(text: str, target_lang: str, page_name: str = "page") -> str
68
+ ```
69
+
70
+ ## Low-level single-string translation with caching.
71
+
72
+ ```
73
+
74
+ detect_browser_lang(accept_language_header: str) -> str
75
+ ```
76
+
77
+ ## Parses Accept-Language → short code like ru, en, fr.
78
+
79
+ ```
80
+ get_alternative_lang(current: str, browser_lang: str) -> str
81
+ ```
82
+
83
+ ## Returns alternate code for a language toggle.
84
+ Cache layout
85
+
86
+ translations/
87
+ └─ page.en.json # UTF-8 JSON: { "source": "translated", ... }
88
+ Example:
89
+
90
+ ``` json (example)
91
+ {
92
+ "Добро пожаловать": "Welcome",
93
+ "Выберите язык:": "Choose a language:"
94
+ }
95
+ ```
96
+ ## Minimal FastAPI wiring (example) python
97
+ ```
98
+ from fastapi import FastAPI, Request
99
+ from autoi18n import Translator
100
+ from pathlib import Path
101
+
102
+ app = FastAPI()
103
+ tr = Translator(source_lang="ru", cache_dir="./translations")
104
+ HTML_PATH = Path("index.html")
105
+
106
+ @app.get("/")
107
+ def home():
108
+ return HTML_PATH.read_text(encoding="utf-8")
109
+
110
+ @app.get("/detect_lang")
111
+ def detect_lang(request: Request):
112
+ return {"lang": tr.detect_browser_lang(request.headers.get("accept-language", ""))}
113
+
114
+ @app.get("/alt_lang")
115
+ def alt_lang(request: Request, current: str = "ru"):
116
+ browser = tr.detect_browser_lang(request.headers.get("accept-language", ""))
117
+ return {"lang": tr.get_alternative_lang(current, browser)}
118
+
119
+ @app.get("/translate")
120
+ def translate(lang: str = "en"):
121
+ html = HTML_PATH.read_text(encoding="utf-8")
122
+ return tr.translate_html(html, target_lang=lang, page_name="page")
123
+ ```
124
+ # Notes & limits
125
+ You control OpenAI usage/billing.
126
+
127
+ Chunking keeps requests within model limits; HTML structure preserved.
128
+
129
+ Short labels (buttons) use a concise prompt to avoid verbose text.
130
+
131
+ This package is for runtime translation with caching; static catalogs are out of scope.
132
+
133
+ # Versioning
134
+ Semantic Versioning: MAJOR.MINOR.PATCH.
135
+
136
+ # License
137
+ MIT © BONA
138
+
139
+ # Support
140
+ Issues & feature requests → GitHub Issues (see project URLs in metadata).
141
+
@@ -0,0 +1,130 @@
1
+ # autoi18n
2
+
3
+ Runtime HTML i18n with OpenAI: translate the page on first request, cache results to JSON, reuse on subsequent requests, and auto-sync when the source changes.
4
+
5
+ **PyPI name:** `autoi18n` | **Import:** `autoi18n`
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ - 🔁 On-the-fly translation of raw HTML strings
12
+ - 🗂️ JSON cache per target language (on disk)
13
+ - 🔄 Auto-sync: stale entries removed when source changes
14
+ - 🧩 Chunking for model context limits (long pages split safely)
15
+ - 🛡️ Preserves `<script>`/`<style>` as-is
16
+ - 🎛️ Concise UI translation (e.g., buttons)
17
+ - 🌐 Helpers: detect browser lang, pick alternate lang
18
+
19
+ ---
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install autoi18n
25
+ ```
26
+ ## Quick start
27
+ ```
28
+ from autoi18n import Translator
29
+
30
+ html_in = "<h1>Добро пожаловать</h1><p>Это тест.</p>"
31
+ tr = Translator(
32
+ source_lang="ru",
33
+ cache_dir="./translations", # JSON cache folder
34
+ # api_key="sk-..." # or use OPENAI_API_KEY env var
35
+ )
36
+
37
+ html_out = tr.translate_html(html_in, target_lang="en", page_name="page")
38
+ print(html_out)
39
+ First call translates via OpenAI and writes ./translations/page.en.json.
40
+ Next calls reuse the cache and only translate new/changed strings.
41
+ ```
42
+ ## Environment
43
+ ```
44
+ OPENAI_API_KEY — used if api_key not passed to Translator.
45
+ ```
46
+ ## API
47
+
48
+ ```
49
+ Translator(source_lang="ru", cache_dir="./translations", api_key=None)
50
+ translate_html(html: str, target_lang: str, page_name: str = "page") -> str
51
+ ```
52
+
53
+ ## Translates visible text nodes, preserves script/style, chunks long text, updates cache JSON.
54
+
55
+ ```
56
+ translate_text(text: str, target_lang: str, page_name: str = "page") -> str
57
+ ```
58
+
59
+ ## Low-level single-string translation with caching.
60
+
61
+ ```
62
+
63
+ detect_browser_lang(accept_language_header: str) -> str
64
+ ```
65
+
66
+ ## Parses Accept-Language → short code like ru, en, fr.
67
+
68
+ ```
69
+ get_alternative_lang(current: str, browser_lang: str) -> str
70
+ ```
71
+
72
+ ## Returns alternate code for a language toggle.
73
+ Cache layout
74
+
75
+ translations/
76
+ └─ page.en.json # UTF-8 JSON: { "source": "translated", ... }
77
+ Example:
78
+
79
+ ``` json (example)
80
+ {
81
+ "Добро пожаловать": "Welcome",
82
+ "Выберите язык:": "Choose a language:"
83
+ }
84
+ ```
85
+ ## Minimal FastAPI wiring (example) python
86
+ ```
87
+ from fastapi import FastAPI, Request
88
+ from autoi18n import Translator
89
+ from pathlib import Path
90
+
91
+ app = FastAPI()
92
+ tr = Translator(source_lang="ru", cache_dir="./translations")
93
+ HTML_PATH = Path("index.html")
94
+
95
+ @app.get("/")
96
+ def home():
97
+ return HTML_PATH.read_text(encoding="utf-8")
98
+
99
+ @app.get("/detect_lang")
100
+ def detect_lang(request: Request):
101
+ return {"lang": tr.detect_browser_lang(request.headers.get("accept-language", ""))}
102
+
103
+ @app.get("/alt_lang")
104
+ def alt_lang(request: Request, current: str = "ru"):
105
+ browser = tr.detect_browser_lang(request.headers.get("accept-language", ""))
106
+ return {"lang": tr.get_alternative_lang(current, browser)}
107
+
108
+ @app.get("/translate")
109
+ def translate(lang: str = "en"):
110
+ html = HTML_PATH.read_text(encoding="utf-8")
111
+ return tr.translate_html(html, target_lang=lang, page_name="page")
112
+ ```
113
+ # Notes & limits
114
+ You control OpenAI usage/billing.
115
+
116
+ Chunking keeps requests within model limits; HTML structure preserved.
117
+
118
+ Short labels (buttons) use a concise prompt to avoid verbose text.
119
+
120
+ This package is for runtime translation with caching; static catalogs are out of scope.
121
+
122
+ # Versioning
123
+ Semantic Versioning: MAJOR.MINOR.PATCH.
124
+
125
+ # License
126
+ MIT © BONA
127
+
128
+ # Support
129
+ Issues & feature requests → GitHub Issues (see project URLs in metadata).
130
+
@@ -0,0 +1,12 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "auto-i18n-lib"
7
+ version = "0.1.0"
8
+ description = "Dynamic HTML translation with OpenAI"
9
+ authors = [{name = "Your Name", email = "you@example.com"}]
10
+ license = {text = "MIT"}
11
+ readme = "README.md"
12
+ requires-python = ">=3.9"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: auto-i18n-lib
3
+ Version: 0.1.0
4
+ Summary: Dynamic HTML translation with OpenAI
5
+ Author-email: Your Name <you@example.com>
6
+ License: MIT
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Dynamic: license-file
11
+
12
+ # autoi18n
13
+
14
+ Runtime HTML i18n with OpenAI: translate the page on first request, cache results to JSON, reuse on subsequent requests, and auto-sync when the source changes.
15
+
16
+ **PyPI name:** `autoi18n` | **Import:** `autoi18n`
17
+
18
+ ---
19
+
20
+ ## Features
21
+
22
+ - 🔁 On-the-fly translation of raw HTML strings
23
+ - 🗂️ JSON cache per target language (on disk)
24
+ - 🔄 Auto-sync: stale entries removed when source changes
25
+ - 🧩 Chunking for model context limits (long pages split safely)
26
+ - 🛡️ Preserves `<script>`/`<style>` as-is
27
+ - 🎛️ Concise UI translation (e.g., buttons)
28
+ - 🌐 Helpers: detect browser lang, pick alternate lang
29
+
30
+ ---
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install autoi18n
36
+ ```
37
+ ## Quick start
38
+ ```
39
+ from autoi18n import Translator
40
+
41
+ html_in = "<h1>Добро пожаловать</h1><p>Это тест.</p>"
42
+ tr = Translator(
43
+ source_lang="ru",
44
+ cache_dir="./translations", # JSON cache folder
45
+ # api_key="sk-..." # or use OPENAI_API_KEY env var
46
+ )
47
+
48
+ html_out = tr.translate_html(html_in, target_lang="en", page_name="page")
49
+ print(html_out)
50
+ First call translates via OpenAI and writes ./translations/page.en.json.
51
+ Next calls reuse the cache and only translate new/changed strings.
52
+ ```
53
+ ## Environment
54
+ ```
55
+ OPENAI_API_KEY — used if api_key not passed to Translator.
56
+ ```
57
+ ## API
58
+
59
+ ```
60
+ Translator(source_lang="ru", cache_dir="./translations", api_key=None)
61
+ translate_html(html: str, target_lang: str, page_name: str = "page") -> str
62
+ ```
63
+
64
+ ## Translates visible text nodes, preserves script/style, chunks long text, updates cache JSON.
65
+
66
+ ```
67
+ translate_text(text: str, target_lang: str, page_name: str = "page") -> str
68
+ ```
69
+
70
+ ## Low-level single-string translation with caching.
71
+
72
+ ```
73
+
74
+ detect_browser_lang(accept_language_header: str) -> str
75
+ ```
76
+
77
+ ## Parses Accept-Language → short code like ru, en, fr.
78
+
79
+ ```
80
+ get_alternative_lang(current: str, browser_lang: str) -> str
81
+ ```
82
+
83
+ ## Returns alternate code for a language toggle.
84
+ Cache layout
85
+
86
+ translations/
87
+ └─ page.en.json # UTF-8 JSON: { "source": "translated", ... }
88
+ Example:
89
+
90
+ ``` json (example)
91
+ {
92
+ "Добро пожаловать": "Welcome",
93
+ "Выберите язык:": "Choose a language:"
94
+ }
95
+ ```
96
+ ## Minimal FastAPI wiring (example) python
97
+ ```
98
+ from fastapi import FastAPI, Request
99
+ from autoi18n import Translator
100
+ from pathlib import Path
101
+
102
+ app = FastAPI()
103
+ tr = Translator(source_lang="ru", cache_dir="./translations")
104
+ HTML_PATH = Path("index.html")
105
+
106
+ @app.get("/")
107
+ def home():
108
+ return HTML_PATH.read_text(encoding="utf-8")
109
+
110
+ @app.get("/detect_lang")
111
+ def detect_lang(request: Request):
112
+ return {"lang": tr.detect_browser_lang(request.headers.get("accept-language", ""))}
113
+
114
+ @app.get("/alt_lang")
115
+ def alt_lang(request: Request, current: str = "ru"):
116
+ browser = tr.detect_browser_lang(request.headers.get("accept-language", ""))
117
+ return {"lang": tr.get_alternative_lang(current, browser)}
118
+
119
+ @app.get("/translate")
120
+ def translate(lang: str = "en"):
121
+ html = HTML_PATH.read_text(encoding="utf-8")
122
+ return tr.translate_html(html, target_lang=lang, page_name="page")
123
+ ```
124
+ # Notes & limits
125
+ You control OpenAI usage/billing.
126
+
127
+ Chunking keeps requests within model limits; HTML structure preserved.
128
+
129
+ Short labels (buttons) use a concise prompt to avoid verbose text.
130
+
131
+ This package is for runtime translation with caching; static catalogs are out of scope.
132
+
133
+ # Versioning
134
+ Semantic Versioning: MAJOR.MINOR.PATCH.
135
+
136
+ # License
137
+ MIT © BONA
138
+
139
+ # Support
140
+ Issues & feature requests → GitHub Issues (see project URLs in metadata).
141
+
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/auto_i18n_lib.egg-info/PKG-INFO
5
+ src/auto_i18n_lib.egg-info/SOURCES.txt
6
+ src/auto_i18n_lib.egg-info/dependency_links.txt
7
+ src/auto_i18n_lib.egg-info/top_level.txt
8
+ src/autoi18n/__init__.py
9
+ src/autoi18n/cache.py
10
+ src/autoi18n/cli.py
11
+ src/autoi18n/glossary.py
12
+ src/autoi18n/translator.py
13
+ src/autoi18n/utils.py
@@ -0,0 +1,2 @@
1
+ # autoi18n package
2
+ from .translator import Translator
File without changes
File without changes
File without changes
@@ -0,0 +1,206 @@
1
+ import os
2
+ import json
3
+ from html.parser import HTMLParser
4
+ from openai import OpenAI
5
+
6
+
7
+ class SimpleHTMLTranslator(HTMLParser):
8
+ def __init__(self, translate_callback):
9
+ super().__init__()
10
+ self.result = []
11
+ self.translate_callback = translate_callback
12
+ self._current_tag = None
13
+ self._inside_skip = False
14
+
15
+ def handle_starttag(self, tag, attrs):
16
+ self._current_tag = tag
17
+ self._inside_skip = False
18
+
19
+ if tag in ("script", "style"):
20
+ self._inside_skip = True
21
+
22
+ # Кнопка langSwitch: пропускаем содержимое, но сам тег сохраняем
23
+ for attr, value in attrs:
24
+ if attr == "id" and value == "langSwitch":
25
+ self._inside_skip = True
26
+
27
+ self.result.append(self.get_starttag_text())
28
+
29
+ def handle_endtag(self, tag):
30
+ if tag in ("script", "style") and self._inside_skip:
31
+ self._inside_skip = False
32
+ if tag == "button" and self._inside_skip:
33
+ self._inside_skip = False
34
+ self.result.append(f"</{tag}>")
35
+
36
+ def handle_data(self, data):
37
+ if self._inside_skip:
38
+ # ⚡ Сохраняем содержимое <script> и <style> без изменений
39
+ self.result.append(data)
40
+ elif self._current_tag == "button":
41
+ # Переводим обычные кнопки
42
+ translated = self.translate_callback(data, prompt_type="button")
43
+ self.result.append(translated)
44
+ else:
45
+ # Переводим обычный текст
46
+ translated = self.translate_callback(data)
47
+ self.result.append(translated)
48
+
49
+ def get_html(self):
50
+ return "".join(self.result)
51
+
52
+
53
+
54
+
55
+ class Translator:
56
+ def __init__(self, cache_dir="./translations", api_key=None):
57
+ # ⚡ Берём язык исходника из .env
58
+ self.source_lang = os.getenv("SOURCE_LANG", "ru")
59
+ self.cache_dir = cache_dir
60
+ self.client = OpenAI(api_key=api_key)
61
+ self._current_file = None
62
+ self._cache = {}
63
+
64
+ def _file_path(self, page_name: str, lang: str) -> str:
65
+ filename = f"{page_name}.{lang}.json"
66
+ return os.path.join(self.cache_dir, filename)
67
+
68
+ def _load_storage(self, page_name: str, lang: str):
69
+ path = self._file_path(page_name, lang)
70
+ if os.path.exists(path):
71
+ print(f"[CACHE] Загружаем переводы из {path}")
72
+ with open(path, "r", encoding="utf-8") as f:
73
+ self._cache = json.load(f)
74
+ else:
75
+ print(f"[CACHE] Файл {path} не найден — создаём новый")
76
+ self._cache = {}
77
+ self._current_file = path
78
+
79
+ def _save_storage(self):
80
+ if self._current_file:
81
+ # ⚡ Создаём папку только при сохранении
82
+ os.makedirs(self.cache_dir, exist_ok=True)
83
+ with open(self._current_file, "w", encoding="utf-8") as f:
84
+ json.dump(self._cache, f, ensure_ascii=False, indent=2)
85
+ print(f"[CACHE] Переводы сохранены в {self._current_file}")
86
+
87
+ def translate_text(self, text: str, target_lang: str, page_name="page", prompt_type="normal") -> str:
88
+ text = text.strip()
89
+ if not text:
90
+ return text
91
+
92
+ if target_lang == self.source_lang:
93
+ return text
94
+
95
+ if not self._current_file:
96
+ self._load_storage(page_name, target_lang)
97
+
98
+ if text in self._cache:
99
+ return self._cache[text]
100
+
101
+ # ⚡ Разные подсказки для кнопок и для обычного текста
102
+ if prompt_type == "button":
103
+ prompt = f"Translate this button label briefly from {self.source_lang} to {target_lang}:\n\n{text}"
104
+ else:
105
+ prompt = f"Translate this text from {self.source_lang} to {target_lang}:\n\n{text}"
106
+
107
+ response = self.client.chat.completions.create(
108
+ model="gpt-4o-mini",
109
+ messages=[{"role": "user", "content": prompt}],
110
+ )
111
+ translated = response.choices[0].message.content.strip()
112
+
113
+ self._cache[text] = translated
114
+ self._save_storage()
115
+
116
+ return translated
117
+
118
+ def translate_html(self, html: str, target_lang: str, page_name="page") -> str:
119
+ # ⚡ Если язык совпадает с исходным — возвращаем как есть
120
+ if target_lang == self.source_lang:
121
+ print(f"[SKIP] {self.source_lang} → {target_lang}: язык совпадает, возвращаем исходную страницу")
122
+ return html
123
+
124
+ # Загружаем кэш
125
+ self._load_storage(page_name, target_lang)
126
+
127
+ current_texts = [] # все тексты из текущей HTML-страницы
128
+
129
+ def translate_with_chunks(text: str, prompt_type: str = "normal") -> str:
130
+ text = text.strip()
131
+ if not text:
132
+ return text
133
+
134
+ current_texts.append(text)
135
+
136
+ # Проверяем кэш
137
+ if text in self._cache:
138
+ print(f"[CACHE-HIT] {self.source_lang} → {target_lang}: «{text[:40]}...»")
139
+ return self._cache[text]
140
+
141
+ # Если текст длинный — режем
142
+ if len(text) > 2000:
143
+ print(f"[SPLIT] Текст длинный ({len(text)} символов), режем на чанки")
144
+ step = 1500
145
+ chunks = [text[i:i + step] for i in range(0, len(text), step)]
146
+ else:
147
+ chunks = [text]
148
+
149
+ translated_parts = []
150
+ for chunk in chunks:
151
+ if prompt_type == "button":
152
+ prompt = f"Translate this button label to {target_lang}. Keep it short, return only the translated label:\n\n{chunk}"
153
+ else:
154
+ prompt = f"Translate to {target_lang}. Return only the translated text, no explanations:\n\n{chunk}"
155
+
156
+ print(f"[OPENAI] {self.source_lang} → {target_lang}: «{chunk[:40]}...»")
157
+ response = self.client.chat.completions.create(
158
+ model="gpt-4o-mini",
159
+ messages=[{"role": "user", "content": prompt}],
160
+ )
161
+ translated_parts.append(response.choices[0].message.content.strip())
162
+
163
+ translated_text = " ".join(translated_parts)
164
+
165
+ self._cache[text] = translated_text
166
+ self._save_storage()
167
+ print(f"[OPENAI-RESULT] «{translated_text[:60]}...»")
168
+
169
+ return translated_text
170
+
171
+ # Парсим HTML
172
+ parser = SimpleHTMLTranslator(
173
+ translate_callback=translate_with_chunks
174
+ )
175
+ parser.feed(html)
176
+
177
+ # ⚡ Синхронизация — удаляем устаревшие строки
178
+ removed = []
179
+ for old_key in list(self._cache.keys()):
180
+ if old_key not in current_texts:
181
+ removed.append(old_key)
182
+ self._cache.pop(old_key)
183
+
184
+ if removed:
185
+ print(f"[SYNC] Удалены устаревшие строки: {len(removed)}")
186
+ self._save_storage()
187
+
188
+ return parser.get_html()
189
+
190
+ def _collect_and_translate(self, text, target_lang, page_name, current_texts):
191
+ text = text.strip()
192
+ if text:
193
+ current_texts.append(text)
194
+ return self.translate_text(text, target_lang, page_name)
195
+
196
+ def detect_browser_lang(self, accept_language: str) -> str:
197
+ """Определяем язык браузера из заголовка Accept-Language"""
198
+ if not accept_language:
199
+ return self.source_lang
200
+ return accept_language.split(",")[0].split("-")[0]
201
+
202
+ def get_alternative_lang(self, current_lang: str, browser_lang: str) -> str:
203
+ """Определяем какой язык должен быть на кнопке"""
204
+ if current_lang == browser_lang:
205
+ return "en" # если мы на языке браузера → кнопка EN
206
+ return browser_lang # если мы на EN → кнопка = язык браузера
File without changes