Translate UI and user-facing messages to Ukrainian (#5)

- All frontend pages, labels, notices and errors; html lang=uk, uk-UA money format
- Brand "Assistant System" in the top bar and page title
- Backend error details returned to the UI (auth, orders, receipts,
  cash registers, Checkbox/CRM/NP errors) and CLI output
- Tests updated for the new messages

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 15:27:39 +03:00
co-authored by Claude Opus 5.5
parent 47df3a78dc
commit b8f2fe6b6e
38 changed files with 232 additions and 230 deletions
+1 -1
View File
@@ -63,7 +63,7 @@ def get_checkbox_client() -> CheckboxClient:
"""Один экземпляр на процесс: внутри — кэш токенов кассиров."""
if settings.checkbox_use_stub:
if settings.is_production:
raise RuntimeError("CHECKBOX_USE_STUB=true запрещён в production")
raise RuntimeError("CHECKBOX_USE_STUB=true заборонено в production")
from app.services.checkbox.stub_client import StubCheckboxClient
return StubCheckboxClient(auto_complete_after=2)
+3 -3
View File
@@ -57,7 +57,7 @@ def _error_message(response: httpx.Response) -> str:
# {"code": "third_party.*", "message": "Internal Server Error"} —
# без кода кассир видит только бесполезное «Internal Server Error».
if code := data.get("code"):
message = f"{message or 'Ошибка'} ({code})"
message = f"{message or 'Помилка'} ({code})"
if isinstance(detail, list) and detail:
parts = [
f"{'.'.join(str(p) for p in item.get('loc', []))}: {item.get('msg')}"
@@ -130,7 +130,7 @@ class HttpCheckboxClient:
method, path, headers=headers, json=json, params=params
)
except httpx.HTTPError as exc:
raise CheckboxUnavailableError(f"Checkbox недоступен: {exc!r}") from exc
raise CheckboxUnavailableError(f"Checkbox недоступний: {exc!r}") from exc
if error := _transient_error(response):
raise error
return response
@@ -155,7 +155,7 @@ class HttpCheckboxClient:
json={"pin_code": creds.pin_code},
)
if response.status_code != 200:
raise CheckboxError(f"Вход кассира не удался: {_error_message(response)}")
raise CheckboxError(f"Вхід касира не вдався: {_error_message(response)}")
token = response.json()["access_token"]
self._tokens[creds.cash_register_id] = token
return token
+3 -3
View File
@@ -32,12 +32,12 @@ class StubCheckboxClient:
async def sign_in(self, creds: CheckboxCredentials) -> None:
if not creds.license_key or not creds.pin_code:
raise CheckboxError("Вход кассира не удался: пустой ключ или PIN")
raise CheckboxError("Вхід касира не вдався: порожній ключ або PIN")
async def create_ettn(self, creds: CheckboxCredentials, body: dict[str, Any]) -> EttnOut:
waybill = body["receipt_body"]["payments"][0]["ettn"]
if await self.find_ettn(creds, waybill) is not None:
raise CheckboxError(f"ЕТТН {waybill} уже привязана к чеку")
raise CheckboxError(f"ЕТТН {waybill} вже прив'язана до чека")
ettn = EttnOut(
id=str(uuid.uuid4()),
status=EttnStatus.CREATED,
@@ -50,7 +50,7 @@ class StubCheckboxClient:
async def get_ettn(self, creds: CheckboxCredentials, ettn_id: str) -> EttnOut:
if ettn_id not in self.orders:
raise CheckboxError(f"ЕТТН-чек {ettn_id} не найден")
raise CheckboxError(f"ЕТТН-чек {ettn_id} не знайдено")
self._polls[ettn_id] = self._polls.get(ettn_id, 0) + 1
if (
self.auto_complete_after is not None