@@ -0,0 +1,123 @@
|
||||
"""Подсказки HTML/HTTP для киоск-Chromium: не предлагать перевод страницы."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Tuple
|
||||
|
||||
from flask import Response
|
||||
|
||||
# Префиксы страниц киоска (localhost / весы / загрузка).
|
||||
# Киоск-страницы, где курсор мыши остаётся видимым (остальные киоск-страницы — cursor: none).
|
||||
_KIOSK_CURSOR_VISIBLE_PATHS: Tuple[str, ...] = (
|
||||
"/scales",
|
||||
)
|
||||
|
||||
_KIOSK_HTML_PATH_PREFIXES: Tuple[str, ...] = (
|
||||
"/scales",
|
||||
"/starting",
|
||||
"/setup",
|
||||
"/calibration",
|
||||
"/calibrate",
|
||||
"/kiosk/",
|
||||
"/static/startup-loading.html",
|
||||
)
|
||||
|
||||
_HEAD_INJECT = (
|
||||
'<meta name="google" content="notranslate">\n'
|
||||
' <meta name="googlebot" content="notranslate">\n'
|
||||
' <meta http-equiv="Content-Language" content="ru">\n'
|
||||
)
|
||||
|
||||
_HTML_TAG_RE = re.compile(r"<html(\s[^>]*)?>", re.IGNORECASE)
|
||||
_HAS_NOTRANSLATE_RE = re.compile(
|
||||
r'<meta[^>]+name=["\']google["\'][^>]+content=["\']notranslate["\']',
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
|
||||
def is_kiosk_html_path(path: str) -> bool:
|
||||
if not path:
|
||||
return False
|
||||
for prefix in _KIOSK_HTML_PATH_PREFIXES:
|
||||
if path == prefix or path.startswith(prefix):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_kiosk_cursor_hidden_path(path: str) -> bool:
|
||||
"""Скрывать курсор на киоск-страницах, кроме явных исключений (например /scales)."""
|
||||
if not is_kiosk_html_path(path):
|
||||
return False
|
||||
normalized = path.rstrip("/") or "/"
|
||||
for visible in _KIOSK_CURSOR_VISIBLE_PATHS:
|
||||
if normalized == visible.rstrip("/") or normalized.startswith(visible.rstrip("/") + "/"):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def kiosk_translate_response_headers() -> dict[str, str]:
|
||||
return {
|
||||
"Content-Language": "ru",
|
||||
"Google-Translate": "no",
|
||||
}
|
||||
|
||||
|
||||
def inject_no_translate_html(html: str) -> str:
|
||||
"""Вставить meta notranslate и translate=no на <html> (обман встроенного переводчика)."""
|
||||
if _HAS_NOTRANSLATE_RE.search(html):
|
||||
patched = html
|
||||
elif "<head>" in html.lower():
|
||||
patched = re.sub(
|
||||
r"<head>",
|
||||
"<head>\n " + _HEAD_INJECT.strip().replace("\n", "\n "),
|
||||
html,
|
||||
count=1,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
else:
|
||||
patched = _HEAD_INJECT + html
|
||||
|
||||
def _html_repl(match: re.Match[str]) -> str:
|
||||
attrs = match.group(1) or ""
|
||||
if re.search(r'\btranslate\s*=', attrs, re.IGNORECASE):
|
||||
return match.group(0)
|
||||
if re.search(r'\bclass\s*=', attrs, re.IGNORECASE):
|
||||
attrs = re.sub(
|
||||
r'class\s*=\s*["\']([^"\']*)["\']',
|
||||
r'class="\1 notranslate"',
|
||||
attrs,
|
||||
count=1,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
else:
|
||||
attrs = f'{attrs} class="notranslate"'
|
||||
if not re.search(r'\blang\s*=', attrs, re.IGNORECASE):
|
||||
attrs = f'{attrs} lang="ru"'
|
||||
return f"<html{attrs} translate=\"no\">"
|
||||
|
||||
return _HTML_TAG_RE.sub(_html_repl, patched, count=1)
|
||||
|
||||
|
||||
def apply_kiosk_translate_hints(response: Response, path: str) -> Response:
|
||||
if not is_kiosk_html_path(path):
|
||||
return response
|
||||
for key, value in kiosk_translate_response_headers().items():
|
||||
response.headers[key] = value
|
||||
content_type = (response.content_type or "").lower()
|
||||
if "text/html" not in content_type:
|
||||
return response
|
||||
# send_from_directory и др. — direct passthrough; get_data() падает с RuntimeError.
|
||||
if getattr(response, "direct_passthrough", False):
|
||||
return response
|
||||
try:
|
||||
html = response.get_data(as_text=True)
|
||||
except (TypeError, UnicodeDecodeError, RuntimeError):
|
||||
return response
|
||||
if not html or "<html" not in html.lower():
|
||||
return response
|
||||
patched = inject_no_translate_html(html)
|
||||
response.set_data(patched)
|
||||
if response.content_length is not None:
|
||||
response.headers.pop("Content-Length", None)
|
||||
return response
|
||||
Reference in New Issue
Block a user