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
+38 -38
View File
@@ -64,7 +64,7 @@ export function CashRegistersPage() {
setMessage({ tone: 'ok', text: okText })
await queryClient.invalidateQueries({ queryKey: ['cash-registers'] })
} catch (err) {
setMessage({ tone: 'error', text: err instanceof Error ? err.message : 'Ошибка запроса' })
setMessage({ tone: 'error', text: err instanceof Error ? err.message : 'Помилка запиту' })
} finally {
setBusy(false)
}
@@ -101,7 +101,7 @@ export function CashRegistersPage() {
if (form.license_key) payload.license_key = form.license_key
if (form.pin_code) payload.pin_code = form.pin_code
if (form.np_api_key) payload.np_api_key = form.np_api_key
await run(() => updateCashRegister(editingId, payload), 'Касса обновлена')
await run(() => updateCashRegister(editingId, payload), 'Касу оновлено')
} else {
await run(
() =>
@@ -114,7 +114,7 @@ export function CashRegistersPage() {
tax_codes: parseTaxCodes(form.tax_codes),
is_default: form.is_default,
}),
'Касса добавлена',
'Касу додано',
)
}
resetForm()
@@ -123,21 +123,21 @@ export function CashRegistersPage() {
return (
<div className="dashboard-shell">
<header className="dashboard-topbar">
<span className="dashboard-brand">lux_fiscal</span>
<span className="dashboard-brand">Assistant System</span>
<Link to="/" className="orders-link-btn">
← К заказам
← До замовлень
</Link>
</header>
<main className="orders-body">
<div className="orders-toolbar">
<h2>Кассы Checkbox</h2>
<h2>Каси Checkbox</h2>
</div>
{message && (
<div className={`orders-notice orders-notice--${message.tone}`}>
<span>{message.text}</span>
<button type="button" className="orders-notice-close" onClick={() => setMessage(null)} aria-label="Скрыть">
<button type="button" className="orders-notice-close" onClick={() => setMessage(null)} aria-label="Сховати">
×
</button>
</div>
@@ -147,34 +147,34 @@ export function CashRegistersPage() {
<table className="orders-table">
<thead>
<tr>
<th>Название</th>
<th>Фиск. номер</th>
<th>Ключ лицензии</th>
<th>Ключ Новой Почты</th>
<th>Налоги</th>
<th>Назва</th>
<th>Фіск. номер</th>
<th>Ключ ліцензії</th>
<th>Ключ Нової Пошти</th>
<th>Податки</th>
<th>Статус</th>
<th>Действия</th>
<th>Дії</th>
</tr>
</thead>
<tbody>
{isLoading && (
<tr>
<td colSpan={7} className="orders-empty">
Загрузка…
Завантаження…
</td>
</tr>
)}
{isError && (
<tr>
<td colSpan={7} className="orders-empty">
Не удалось загрузить кассы
Не вдалося завантажити каси
</td>
</tr>
)}
{registers?.length === 0 && (
<tr>
<td colSpan={7} className="orders-empty">
Кассы не настроены — чеки создавать нельзя
Каси не налаштовані — створювати чеки неможливо
</td>
</tr>
)}
@@ -182,15 +182,15 @@ export function CashRegistersPage() {
<tr key={register.id}>
<td>
{register.name}
{register.is_default && <span className="orders-status orders-status--delivered cr-default">основная</span>}
{register.is_default && <span className="orders-status orders-status--delivered cr-default">основна</span>}
</td>
<td>{register.fiscal_number || '—'}</td>
<td>{register.license_key_masked}</td>
<td>{register.np_api_key_masked ?? <span className="cr-missing">не задан</span>}</td>
<td>{register.tax_codes.length ? register.tax_codes.join(', ') : 'без налога'}</td>
<td>{register.np_api_key_masked ?? <span className="cr-missing">не задано</span>}</td>
<td>{register.tax_codes.length ? register.tax_codes.join(', ') : 'без податку'}</td>
<td>
<span className={`orders-status orders-status--${register.is_active ? 'delivered' : 'new'}`}>
{register.is_active ? 'Активна' : 'Отключена'}
{register.is_active ? 'Активна' : 'Вимкнена'}
</span>
</td>
<td className="orders-actions-cell">
@@ -198,12 +198,12 @@ export function CashRegistersPage() {
type="button"
className="orders-view-btn"
disabled={busy}
onClick={() => void run(() => checkCashRegister(register.id), `Вход в «${register.name}» успешен`)}
onClick={() => void run(() => checkCashRegister(register.id), `Вхід у «${register.name}» успішний`)}
>
Проверить
Перевірити
</button>
<button type="button" className="orders-view-btn" disabled={busy} onClick={() => startEdit(register)}>
Изменить
Змінити
</button>
<button
type="button"
@@ -212,11 +212,11 @@ export function CashRegistersPage() {
onClick={() =>
void run(
() => updateCashRegister(register.id, { is_active: !register.is_active }),
register.is_active ? 'Касса отключена' : 'Касса включена',
register.is_active ? 'Касу вимкнено' : 'Касу увімкнено',
)
}
>
{register.is_active ? 'Отключить' : 'Включить'}
{register.is_active ? 'Вимкнути' : 'Увімкнути'}
</button>
</td>
</tr>
@@ -226,48 +226,48 @@ export function CashRegistersPage() {
</div>
<form className="cr-form" onSubmit={(e) => void handleSubmit(e)}>
<h3>{editingId ? 'Изменить кассу' : 'Добавить кассу'}</h3>
<h3>{editingId ? 'Змінити касу' : 'Додати касу'}</h3>
<label>
Название
Назва
<input required value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
</label>
<label>
Фискальный номер
Фіскальний номер
<input value={form.fiscal_number} onChange={(e) => setForm({ ...form, fiscal_number: e.target.value })} />
</label>
<label>
Ключ лицензии кассы
Ключ ліцензії каси
<input
required={!editingId}
autoComplete="off"
placeholder={editingId ? 'не менять' : ''}
placeholder={editingId ? 'не змінювати' : ''}
value={form.license_key}
onChange={(e) => setForm({ ...form, license_key: e.target.value })}
/>
</label>
<label>
PIN-код кассира
PIN-код касира
<input
required={!editingId}
type="password"
autoComplete="new-password"
placeholder={editingId ? 'не менять' : ''}
placeholder={editingId ? 'не змінювати' : ''}
value={form.pin_code}
onChange={(e) => setForm({ ...form, pin_code: e.target.value })}
/>
</label>
<label>
Ключ API Новой Почты (посылки этого кабинета пробиваются через эту кассу)
Ключ API Нової Пошти (посилки цього кабінету пробиваються через цю касу)
<input
required={!editingId}
autoComplete="off"
placeholder={editingId ? 'не менять' : ''}
placeholder={editingId ? 'не змінювати' : ''}
value={form.np_api_key}
onChange={(e) => setForm({ ...form, np_api_key: e.target.value })}
/>
</label>
<label>
Коды налогов (через запятую, пусто — без налога)
Коди податків (через кому, порожньо — без податку)
<input value={form.tax_codes} onChange={(e) => setForm({ ...form, tax_codes: e.target.value })} />
</label>
<label className="cr-checkbox">
@@ -276,15 +276,15 @@ export function CashRegistersPage() {
checked={form.is_default}
onChange={(e) => setForm({ ...form, is_default: e.target.checked })}
/>
Основная касса (первой проверяет новые посылки)
Основна каса (першою перевіряє нові посилки)
</label>
<div className="cr-form-actions">
<button type="submit" className="orders-create-btn" disabled={busy}>
{editingId ? 'Сохранить' : 'Добавить'}
{editingId ? 'Зберегти' : 'Додати'}
</button>
{editingId && (
<button type="button" className="orders-view-btn" onClick={resetForm}>
Отмена
Скасувати
</button>
)}
</div>