Files
mdm-ordinis/ordinis-admin-ui/src/i18n.ts
T
Александр Зимин 37c9ae7040 Merge branch 'fix/qa-report-sweep' into 'main'
fix(admin-ui): QA report sweep — modal/table layout, i18n, sidebar polish

See merge request 2-6/2-6-4/terravault/ordinis!239
2026-05-17 12:37:29 +00:00

1744 lines
119 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'ru-RU',
supportedLngs: ['ru-RU', 'en-US'],
interpolation: { escapeValue: false },
resources: {
'ru-RU': {
translation: {
'app.title': 'Ordinis',
'theme.label': 'Тема',
'theme.light': 'Светлая',
'theme.dark': 'Тёмная',
'theme.system': 'Системная',
'nav.docs': 'Руководство',
'nav.home': 'Главная',
'nav.graph': 'Граф связей',
'nav.section.workflow': 'Workflow',
'nav.section.admin': 'Администрирование',
'sidebar.collapse': 'Свернуть',
'sidebar.expand': 'Развернуть',
'topbar.search.label': 'Поиск',
'topbar.search.placeholder': 'Поиск по справочникам, ⌘K',
'topbar.notifications': 'Уведомления',
'whatsnew.button': 'Что нового',
'whatsnew.title': 'Что нового в Ordinis',
'whatsnew.description': 'Последние релизы продукта. Обновления автоматически попадают на ваш сервер при следующем deploy.',
'whatsnew.unread_one': '{{count}} новое',
'whatsnew.unread_few': '{{count}} новых',
'whatsnew.unread_many': '{{count}} новых',
'whatsnew.unread_other': '{{count}} новых',
'whatsnew.newBadge': 'новое',
'whatsnew.empty': 'Список релизов пока пуст.',
'whatsnew.entryEmpty': 'Описание не указано.',
'whatsnew.footer': 'Полный список изменений — см. CHANGELOG.md в репозитории.',
'graph.title': 'Граф связей справочников',
'graph.description': 'Сетевая диаграмма всех справочников и их FK-связей. Кликните по узлу — откроется детальный вид.',
'graph.comingSoon.title': 'Скоро',
'graph.comingSoon.description': 'Visualization через d3-force — 40+ справочников с linked edges. В разработке (Stage 3.3 redesign roadmap).',
'graph.search.placeholder': 'Найти справочник…',
'graph.search.empty': 'ничего не найдено',
'graph.search.count': '{{count}} из {{total}}',
'updateBanner.message': 'Доступна новая версия НСИ ({{version}}). Обновите страницу, чтобы получить последние исправления.',
'updateBanner.messageGeneric': 'Доступна новая версия НСИ. Обновите страницу, чтобы получить последние исправления.',
'updateBanner.reload': 'Обновить',
'updateBanner.docs': 'Что нового',
'updateBanner.dismiss': 'Закрыть уведомление',
'nav.dictionaries': 'Справочники',
'nav.audit': 'Аудит',
'nav.outbox': 'Outbox',
'nav.webhooks': 'Webhooks',
'nav.search': 'Поиск',
'nav.reviews': 'Согласования',
'nav.myDrafts': 'Мои черновики',
'myDrafts.title': 'Мои черновики',
'myDrafts.description': 'Все ваши черновики с историей: ожидает рассмотрения, одобрен, отклонён, отозван.',
'myDrafts.empty': 'Вы пока не отправляли черновиков.',
'myDrafts.total_one': '{{count}} черновик',
'myDrafts.total_few': '{{count}} черновика',
'myDrafts.total_many': '{{count}} черновиков',
'myDrafts.total_other': '{{count}} черновиков',
'myDrafts.col.bk': 'Бизнес-ключ',
'myDrafts.col.op': 'Операция',
'myDrafts.col.status': 'Статус',
'myDrafts.col.submitted': 'Отправлено',
'myDrafts.col.reviewed': 'Решено',
'myDrafts.col.reviewer': 'Рецензент',
'myDrafts.col.comment': 'Комментарий',
'myDrafts.col.actions': 'Действия',
'myDrafts.action.withdraw': 'Отозвать',
'myDrafts.confirmWithdraw': 'Отозвать draft? Это безвозвратно — статус станет WITHDRAWN.',
'reviews.title': 'Очередь согласований',
'reviews.description': 'Черновики от авторов, ждут одобрения или отклонения. Общая очередь: первый рецензент закрывает заявку.',
'reviews.empty': 'Нет черновиков на согласование. Все обработаны 👍',
'reviews.queueTotal_one': '{{count}} черновик в очереди',
'reviews.queueTotal_few': '{{count}} черновика в очереди',
'reviews.queueTotal_many': '{{count}} черновиков в очереди',
'reviews.queueTotal_other': '{{count}} черновиков в очереди',
'reviews.col.dict': 'Справочник',
'reviews.col.bk': 'Business key',
'reviews.col.op': 'Операция',
'reviews.col.maker': 'Автор',
'reviews.col.submitted': 'Отправлен',
'reviews.col.actions': 'Действия',
'reviews.action.review': 'Просмотр',
'reviews.action.approve': 'Approve',
'reviews.action.reject': 'Reject',
'reviews.action.cancel': 'Отмена',
'reviews.bulk.selectAll': 'Выбрать все',
'reviews.bulk.selectRow': 'Выбрать draft',
'reviews.bulk.selectedCount_one': 'выбран {{count}}',
'reviews.bulk.selectedCount_few': 'выбрано {{count}}',
'reviews.bulk.selectedCount_many': 'выбрано {{count}}',
'reviews.bulk.selectedCount_other': 'выбрано {{count}}',
'reviews.bulk.approveSelected': 'Approve выбранные',
'reviews.bulk.rejectSelected': 'Reject выбранные',
'reviews.bulk.clear': 'Снять выбор',
'reviews.bulk.confirmApprove_one': 'Approve {{count}} draft? Это произведёт live-write для каждого.',
'reviews.bulk.confirmApprove_few': 'Approve {{count}} draft\'а? Live-write для каждого.',
'reviews.bulk.confirmApprove_many': 'Approve {{count}} draft\'ов? Live-write для каждого.',
'reviews.bulk.confirmApprove_other': 'Approve {{count}} drafts? Live-write для каждого.',
'reviews.bulk.rejectModalTitle_one': 'Reject {{count}} draft',
'reviews.bulk.rejectModalTitle_few': 'Reject {{count}} draft\'а',
'reviews.bulk.rejectModalTitle_many': 'Reject {{count}} draft\'ов',
'reviews.bulk.rejectModalTitle_other': 'Reject {{count}} drafts',
'reviews.bulk.rejectModalDescription': 'Все выбранные drafts будут отклонены с одним общим reason\'ом. Maker увидит причину в "Мои черновики".',
'reviews.bulk.rejectReasonLabel': 'Причина (общая для всех)',
'reviews.bulk.rejectReasonPlaceholder': 'Например: дубликат регистрации, неполные данные, не тот scope...',
'reviews.bulk.confirmReject_one': 'Reject {{count}}',
'reviews.bulk.confirmReject_other': 'Reject {{count}}',
'reviews.bulk.cancel': 'Отмена',
'reviews.bulk.resultTitle': 'Bulk операция завершена',
'reviews.bulk.approvedCount_one': 'Approved {{count}}',
'reviews.bulk.approvedCount_other': 'Approved {{count}}',
'reviews.bulk.rejectedCount_one': 'Rejected {{count}}',
'reviews.bulk.rejectedCount_other': 'Rejected {{count}}',
'reviews.bulk.failedCount_one': 'Не прошло {{count}}',
'reviews.bulk.failedCount_other': 'Не прошло {{count}}',
'reviews.drawer.title': 'Согласование draft',
'reviews.drawer.maker': 'автор',
'reviews.drawer.submitted': 'отправлен',
'reviews.drawer.makerComment': 'Комментарий автора',
'reviews.drawer.live': 'Текущая (live)',
'reviews.drawer.proposed': 'Предложено',
'reviews.drawer.noLive': 'Нет live-записи (CREATE).',
'reviews.drawer.closeNote': 'CLOSE: запись будет закрыта (valid_to=now).',
'reviews.drawer.comment': 'Комментарий ревьюера',
'reviews.drawer.commentPlaceholder': 'Для approve опционально, для reject обязательно',
'schema.approvalRequired.label': 'Требовать approval (maker-checker)',
'schema.approvalRequired.hint': 'Approval Workflow v2. Все CRUD операции пойдут через draft → review → approve. Защита от operator error на критичных справочниках. Существующие маршруты (POST/PUT/DELETE) вернут 409 draft_required с pointer на /drafts API.',
'schema.approvalRequired.editorBlockTitle': 'Изменения требуют ревью',
'schema.approvalRequired.editorBlock': 'Этот словарь требует maker-checker review. Откройте черновик — ваши текущие изменения будут предзаполнены, вам нужно только указать причину и отправить на согласование.',
'schema.approvalRequired.openDraftCta': 'Создать черновик с моими изменениями',
'schema.approvalMinRole.label': 'Минимальная роль ревьюера (опционально)',
'schema.approvalMinRole.hint': 'Например ordinis:internal или ordinis:restricted. Если задано — backend требует чтобы ревьюер имел эту роль (поверх стандартных ordinis:record:reviewer / ordinis:schema:reviewer) для approve / reject / publish. Пусто = достаточно стандартной reviewer role. ordinis:admin проходит всегда.',
'search.title': 'Smart search',
'search.description': 'Поиск по содержимому всех справочников. Активные записи, текущий момент. Min 3 символа (trigram index).',
'search.placeholder': 'Введите 3+ символа: код, название, идентификатор…',
'search.tooShort': 'Минимум 3 символа для запуска поиска.',
'search.empty': 'Ничего не найдено. Попробуйте другой запрос.',
'search.totalHits_one': 'Найдено: {{count}} запись',
'search.totalHits_few': 'Найдено: {{count}} записи',
'search.totalHits_many': 'Найдено: {{count}} записей',
'search.totalHits_other': 'Найдено: {{count}} записей',
'search.intro': 'Введите запрос и нажмите Enter. Группировка результатов по справочнику; max 10 на справочник, 100 всего.',
'webhooks.title': 'Webhook subscriptions',
'webhooks.subtitle': 'HTTP-доставка событий не-Kafka подписчикам',
'webhooks.empty': 'Нет подписок. Создайте первую через кнопку справа.',
'webhooks.active': 'Активна',
'webhooks.inactive': 'Отключена',
'webhooks.allScopes': 'все scope',
'webhooks.allDicts': 'все справочники',
'webhooks.allEvents': 'все события',
'webhooks.notFound': 'Подписка не найдена',
'webhooks.col.name': 'Имя',
'webhooks.col.url': 'URL',
'webhooks.col.scopes': 'Scope filter',
'webhooks.col.dictionaries': 'Справочники',
'webhooks.col.status': 'Статус',
'webhooks.action.create': 'Создать',
'webhooks.action.delete': 'Удалить',
'webhooks.action.rotateSecret': 'Сменить secret',
'webhooks.action.test': 'Тест ping',
'webhooks.test.success': 'Receiver получил test event и ответил 2xx',
'webhooks.test.failure': 'Receiver не принял test event',
'webhooks.test.rejected': 'Запрос не отправлен (SSRF guard / serialization)',
'webhooks.test.latency': 'latency',
'webhooks.confirmDelete': 'Удалить подписку «{{name}}»? Все pending deliveries удалятся каскадом.',
'webhooks.confirmRotate': 'Сменить HMAC secret? Старый сразу перестанет работать — receiver надо обновить заранее.',
'webhooks.field.name': 'Имя подписки',
'webhooks.field.url': 'Webhook URL',
'webhooks.field.urlHint': 'https:// или http://. Private CIDR (10/8, 192.168/16, 127/8) запрещены SSRF guard',
'webhooks.field.scopeFilter': 'Scope filter',
'webhooks.field.scopeFilterHint': 'Пусто = все scope',
'webhooks.field.dictionaryFilter': 'Справочники (CSV)',
'webhooks.field.dictionaryFilterHint': 'Через запятую: spacecraft, satellite_type. Пусто = все',
'webhooks.field.eventTypeFilter': 'Типы событий',
'webhooks.field.eventTypeFilterHint': 'Record*, Definition*, SchemaDraft*. Пусто = все события.',
'webhooks.field.eventTypeSearch': 'Поиск типа события',
'webhooks.field.description': 'Описание',
'webhooks.field.createdBy': 'Создатель',
'webhooks.error.nameUrlRequired': 'Имя и URL обязательны',
'webhooks.error.urlScheme': 'URL должен начинаться с http:// или https://',
'webhooks.secret.title': 'HMAC secret для «{{name}}»',
'webhooks.secret.warningTitle': 'Скопируй сейчас!',
'webhooks.secret.warning': 'Этот secret показывается ОДИН раз. Дальше будет замаскирован. Передай receiver чтобы он мог проверять X-Ordinis-Signature header.',
'webhooks.secret.rotatedTitle': 'Secret обновлён',
'webhooks.secret.rotatedWarning': 'Новый secret. Старый перестал работать. Обнови receiver.',
'webhooks.secret.copy': 'Копировать',
'webhooks.secret.copied': 'Скопировано',
'webhooks.secret.acknowledge': 'Сохранил',
'webhooks.deliveries.title': 'История доставок',
'webhooks.deliveries.empty': 'Доставок ещё не было',
'webhooks.stats.pending': 'В очереди',
'webhooks.stats.retrying': 'Retry',
'webhooks.stats.delivered': 'Доставлено',
'webhooks.stats.dlq': 'DLQ',
'webhooks.stats.successRate': 'Success rate',
'webhooks.stats.successRateOf_one': 'из {{count}} завершённой',
'webhooks.stats.successRateOf_few': 'из {{count}} завершённых',
'webhooks.stats.successRateOf_many': 'из {{count}} завершённых',
// Status labels (used by histogram chart legend + tooltip)
'webhooks.status.pending': 'в очереди',
'webhooks.status.retrying': 'retry',
'webhooks.status.delivered': 'доставлено',
'webhooks.status.dlq': 'DLQ',
// Histogram chart
'webhooks.histogram.title': 'История доставок',
'webhooks.histogram.bucket.hour': '24ч',
'webhooks.histogram.bucket.day': '7д',
'webhooks.histogram.empty': 'Нет доставок за выбранный период.',
'webhooks.histogram.total': 'итого',
// === Friendly query error states ===
'queryError.retry': 'Повторить',
'queryError.network.title': 'Нет соединения с сервером',
'queryError.network.body': 'Проверьте сеть. Когда соединение восстановится — повторите.',
'queryError.unavailable.title': 'Сервер временно недоступен',
'queryError.unavailable.body': 'Возможно идёт обновление или плановое обслуживание. Подождите несколько секунд и повторите.',
'queryError.server.title': 'Внутренняя ошибка сервера',
'queryError.server.body': 'Что-то пошло не так на стороне сервера. Если ошибка повторяется — сообщите администратору.',
'queryError.notFound.title': 'Не найдено',
'queryError.notFound.body': 'Запрашиваемые данные не существуют или были удалены.',
'queryError.forbidden.title': 'Нет доступа',
'queryError.forbidden.body': 'У вас нет прав на просмотр этих данных.',
'queryError.unauthorized.title': 'Требуется вход',
'queryError.unauthorized.body': 'Сессия истекла. Войдите снова чтобы продолжить.',
'queryError.client.title': 'Запрос некорректен',
'queryError.client.body': 'Сервер отклонил запрос.',
'queryError.generic.title': 'Не удалось загрузить данные',
'queryError.generic.body': 'Что-то пошло не так. Попробуйте ещё раз.',
'timeTravel.empty.noEvents': 'Между этими моментами ничего не менялось.',
'timeTravel.action.diff': 'Diff',
'timeTravel.records.empty': 'На {{date}} записей не было.',
'timeTravel.records.title_one': '{{count}} запись на {{date}}',
'timeTravel.records.title_few': '{{count}} записи на {{date}}',
'timeTravel.records.title_many': '{{count}} записей на {{date}}',
'timeTravel.records.col.name': 'Название',
'timeTravel.records.col.validFrom': 'Действует с',
'timeTravel.records.truncated': 'Показано {{visible}} из {{total}}. Откройте как readonly для полного списка.',
'timeTravel.schema.fieldsTitle': 'Поля справочника на v{{version}}',
'timeTravel.schema.empty': 'Полей нет.',
'timeTravel.schema.fallback': 'Показана live-схема — diff на эту версию недоступен.',
'timeTravel.schema.historyTitle': 'История этой версии',
'timeTravel.schema.author': 'автор',
'timeTravel.schema.date': 'дата',
'timeTravel.schema.noHistory': 'История недоступна для выбранного момента.',
'webhooks.deliveries.col.eventId': 'Event ID',
'webhooks.deliveries.col.attempts': 'Попыток',
'webhooks.deliveries.col.lastAttempt': 'Последняя попытка',
'webhooks.deliveries.col.lastStatusCode': 'HTTP статус',
'webhooks.deliveries.col.lastError': 'Последняя ошибка',
'webhooks.deliveries.action.retry': 'Повторить доставку',
'webhooks.deliveries.confirmRetry': 'Сбросить attemptCount=0 и поставить в очередь повтор? Receiver получит payload снова.',
'webhooks.deliveries.filter.label': 'Фильтр по статусу',
'webhooks.deliveries.filter.clear': 'Сбросить',
'audit.title': 'Журнал аудита',
'audit.subtitle': 'Юридически значимый лог изменений НСИ',
'audit.filter.dictionary': 'Справочник',
'audit.filter.user': 'Пользователь',
'audit.filter.action': 'Действие',
'audit.filter.businessKey': 'Бизнес-ключ',
'audit.filter.from': 'С',
'audit.filter.to': 'По',
'audit.filter.range': 'Период',
'audit.filter.reset': 'Сбросить',
'audit.filter.apply': 'Применить',
'audit.filter.active': 'Активные фильтры',
'audit.filter.removeChip': 'Убрать фильтр',
'audit.col.time': 'Время',
'audit.col.action': 'Действие',
'audit.col.dictionary': 'Справочник',
'audit.col.businessKey': 'Бизнес-ключ',
'audit.col.user': 'Пользователь',
// 'Scope' колонка показывает scope актора (claim из JWT), а НЕ
// scope ресурса. Backend audit log не хранит scope dictionary'я.
// User confusion: 'правки в public были, а тут RESTRICTED' —
// потому что у пользователя scope=RESTRICTED привилегия. Label
// 'Доступ актора' разъясняет это.
'audit.col.scope': 'Scope',
'audit.col.trace': 'Trace',
'audit.col.diff': 'Изменения',
'audit.action.expand': 'Раскрыть',
'audit.action.export': 'Экспорт CSV',
'audit.empty': 'Нет записей по выбранным фильтрам',
'audit.action.CREATE': 'Создание записи',
'audit.action.UPDATE': 'Изменение записи',
'audit.action.CLOSE': 'Закрытие записи',
'audit.action.DEFINITION_CREATE': 'Создание справочника',
'audit.action.DEFINITION_UPDATE': 'Изменение схемы',
'audit.action.DRAFT_CREATE': 'Черновик создан',
'audit.action.DRAFT_SUBMIT': 'Черновик отправлен',
'audit.action.DRAFT_REVIEW': 'Черновик в очереди',
'audit.action.DRAFT_APPROVE': 'Черновик одобрен',
'audit.action.DRAFT_REJECT': 'Черновик отклонён',
'audit.action.DRAFT_CHANGES_REQUESTED': 'Запрошены правки',
'audit.action.DRAFT_WITHDRAW': 'Черновик отозван',
'audit.action.DRAFT_PUBLISH': 'Черновик опубликован',
'audit.diff.before': 'Было',
'audit.diff.after': 'Стало',
'audit.page.of': 'Стр. {{cur}} из {{total}}',
'audit.page.size': 'На странице',
'audit.page.prev': 'Назад',
'audit.page.next': 'Вперёд',
'outbox.title': 'Outbox events',
'outbox.subtitle': 'Очередь публикации в Kafka и DLQ',
'outbox.stats.pending': 'В очереди',
'outbox.stats.dlq': 'В DLQ',
'outbox.dlq.col.id': 'ID',
'outbox.dlq.col.eventType': 'Тип',
'outbox.dlq.col.dictionary': 'Справочник',
'outbox.dlq.col.aggregateId': 'Aggregate ID',
'outbox.dlq.col.topic': 'Topic',
'outbox.dlq.col.attempts': 'Попыток',
'outbox.dlq.col.lastError': 'Последняя ошибка',
'outbox.dlq.col.dlqAt': 'В DLQ с',
'outbox.dlq.empty': 'DLQ пуста — все события публикуются успешно',
'header.scope': 'Доступный scope',
'dict.list.subtitle': 'Каталоги НСИ ЦУОД ОДХ',
'dict.list.search.placeholder': 'Поиск по названию, коду или описанию',
'dict.list.search.empty': 'Ничего не найдено',
'dict.list.found': 'Показано {{shown}} из {{total}}',
'dict.list.recordCount_one': '{{count}} запись',
'dict.list.recordCount_few': '{{count}} записи',
'dict.list.recordCount_many': '{{count}} записей',
'dict.list.recordCount_other': '{{count}} записей',
'dict.list.section.PUBLIC': 'Публичные',
'dict.list.section.INTERNAL': 'Внутренние',
'dict.list.section.RESTRICTED': 'Ограниченные',
'dict.list.section.PUBLIC.short': 'PUB',
'dict.list.section.INTERNAL.short': 'INT',
'dict.list.section.RESTRICTED.short': 'RES',
'dict.list.deps.short': 'СО СВЯЗЯМИ',
// Catalog v2 (URL-driven filters + sort + density)
'dict.list.heading': 'Каталог справочников',
'dict.list.filter.scope': 'Фильтр по области (можно выбрать несколько)',
'dict.list.sort.label': 'Сортировка',
'dict.list.sort.name': 'По названию',
'dict.list.sort.recordCount': 'По количеству записей',
'dict.list.sort.updated': 'По обновлению',
'dict.list.density.label': 'Плотность',
'dict.list.density.card': 'Карточки',
'dict.list.density.dense': 'Список',
'dict.list.search.reset': 'Сбросить фильтры',
'dict.list.approval': 'approval',
'dict.list.col.name': 'Название',
'dict.list.col.bundle': 'Bundle',
'dict.list.col.version': 'Версия',
'dict.list.col.locales': 'Локали',
'dict.list.col.records': 'Записи',
// Compact row table (catalog) — column headers per redesign/compact.html.
// QA bug #5: defaultValue inline в коде падал на ru-text при EN locale;
// выносим в i18n чтобы переключение языка реально влияло на заголовки.
'dict.col.title': 'Название',
'dict.col.id': 'id',
'dict.col.bundle': 'bundle',
'dict.col.records': 'записей',
'dict.col.updated': 'изменён',
'dict.col.outgoingFk': 'Ссылается (исходящие FK)',
'dict.col.incomingFk': 'Используют (входящие FK)',
// List view (handoff D)
'dict.list.bundle.all': 'все',
'dict.list.graph': 'Граф',
'dict.list.deps.only': 'Только со связями',
'dict.list.records.short': 'справочн.',
'dict.list.fk.references': '→ ссылается',
'dict.list.fk.usedBy_one': '← {{count}} использ.',
'dict.list.fk.usedBy_few': '← {{count}} использ.',
'dict.list.fk.usedBy_many': '← {{count}} использ.',
'dict.list.fk.usedBy_other': '← {{count}} использ.',
'dict.empty': 'В этом справочнике пока нет записей',
'dict.scheduled.hint_one':
'Запланирована {{count}} запись на {{date}}',
'dict.scheduled.hint_few':
'Запланировано {{count}} записи, ближайшая на {{date}}',
'dict.scheduled.hint_many':
'Запланировано {{count}} записей, ближайшая на {{date}}',
'dict.scheduled.hint_other':
'Запланировано {{count}} записей, ближайшая на {{date}}',
'dict.scheduled.cta': 'Перейти к этой дате',
'dict.scheduled.row.badge': 'Запланировано',
'dict.scheduled.row.tooltip': 'У записи есть запланированная будущая версия',
'aiSuggest.openButton': 'AI: добавить поле',
'aiSuggest.title': 'AI: добавить поле',
'aiSuggest.placeholder': 'Опиши поле — например «орбита: апогей, перигей, наклонение»',
'aiSuggest.hint': 'AI предложит структуру (type/format/x-references). GOST коды и validations добавь сам.',
'aiSuggest.suggest': 'Предложить',
'aiSuggest.thinking': 'Думаю…',
'aiSuggest.preview': 'Предложение',
'aiSuggest.retry': 'Перегенерировать',
'aiSuggest.accept': 'Добавить в схему',
'aiSuggest.error.circuit': 'AI временно недоступен. Попробуй через несколько минут.',
'aiSuggest.error.rate': 'Слишком много запросов. Подожди минуту.',
'aiSuggest.error.bad_output': 'AI вернул невалидный ответ. Попробуй переформулировать.',
'aiSuggest.error.upstream': 'LLM endpoint недоступен. Жди restore или попробуй позже.',
'aiSuggest.error.disabled': 'AI отключён в этой инсталляции.',
'aiSuggest.error.generic': 'Не удалось получить предложение.',
'schemaTemplates.label': 'Начать с шаблона',
'schemaTemplates.hint': 'Шаблон загрузит начальные поля. Дополни их вручную.',
'common.close': 'Закрыть',
'dict.col.businessKey': 'Бизнес-ключ',
'dict.col.scope': 'Scope',
'dict.col.validFrom': 'Действует с',
'dict.col.locale': 'Локаль',
'dict.col.actions': 'Действия',
'dict.pendingReview.label': 'На review',
'dict.pendingReview.tooltip': 'Есть pending draft на эту запись. Откройте /reviews для approve/reject.',
'dict.list.records': 'записей',
'dict.action.create': 'Создать',
'dict.action.edit': 'Изменить',
'dict.action.close': 'Закрыть',
'dict.confirmClose.title': 'Закрыть запись?',
'dict.confirmClose.body':
'Запись «{{key}}» будет помечена как закрытая (valid_to = now). Действие обратимо через создание новой версии.',
'dict.confirmClose.reason': 'Причина (опционально)',
'dict.confirmClose.reasonPlaceholder': 'например, «дубликат» или «выведено из эксплуатации»',
'dict.bulk.selectAll': 'Выбрать все на странице',
'dict.bulk.selectRow': 'Выбрать запись',
'dict.bulk.selectedCount_one': 'Выбрано {{count}} из {{total}}',
'dict.bulk.selectedCount_few': 'Выбрано {{count}} из {{total}}',
'dict.bulk.selectedCount_many': 'Выбрано {{count}} из {{total}}',
'dict.bulk.selectedCount_other': 'Выбрано {{count}} из {{total}}',
'dict.bulk.clearSelection': 'Снять выделение',
'dict.bulk.closeAction': 'Закрыть выбранные',
'dict.bulk.exportAction': 'Экспорт CSV',
'dict.page.prev': 'Назад',
'dict.page.next': 'Вперёд',
'dict.page.of': 'Стр. {{cur}} из {{total}}',
'dict.page.range': 'Записи {{from}}{{to}} из {{total}}',
'dict.bulk.confirmTitle': 'Массовое закрытие записей',
'dict.bulk.confirmBody':
'{{count}} запис(ей) будут помечены как закрытые (valid_to = now). Действие обратимо через создание новой версии. Каждая запись закрывается отдельно — если часть уже закрыта, остальные обработаются.',
'dict.bulk.confirmAction': 'Закрыть {{count}}',
'dict.bulk.resultClosed': 'Закрыто: {{count}}',
'dict.bulk.resultSkipped': 'Пропущено (уже закрыто или не найдено): {{count}}',
'dict.bulk.resultErrors': 'Ошибок: {{count}}',
'dict.bulk.dismiss': 'Готово',
'form.identity': 'Идентификация',
'form.idSourceNote': 'Бизнес-ключ создастся автоматически из поля «{{field}}»',
'form.tabs.identity': 'Идентификация',
'form.tabs.description': 'Описание',
'form.tabs.extra': 'Дополнительно',
'form.tabs.references': 'Связи',
'form.tabs.dates': 'Даты',
'form.tabs.geometry': 'Геометрия',
'form.tabs.physical': 'Физические параметры',
'form.businessKey': 'Бизнес-ключ',
'form.validFrom': 'Действует с',
'form.validFromHint': 'Когда запись становится активной. Пусто = с момента создания.',
'form.validTo': 'Действует до',
'form.validToHint': 'Когда перестаёт действовать. Пусто = бессрочно.',
'form.error.validToBeforeFrom': 'Дата окончания должна быть позже даты начала.',
'form.dataFields': 'Поля записи',
'form.cancel': 'Отмена',
'form.save': 'Сохранить',
'form.saving': 'Сохранение...',
'form.delete': 'Закрыть запись',
'form.error.required': 'Обязательное поле',
'form.unsavedNFields_one': 'не сохранено · {{count}} поле',
'form.unsavedNFields_few': 'не сохранено · {{count}} поля',
'form.unsavedNFields_many': 'не сохранено · {{count}} полей',
'form.unsavedNFields_other': 'не сохранено · {{count}} полей',
'drawer.collapse': 'Свернуть до 520px',
'drawer.expand': 'Развернуть до 880px',
'drawer.close': 'Закрыть',
'drawer.captionEdit': 'редактирование записи · {{dict}}',
'drawer.captionCreate': 'новая запись · {{dict}}',
'drawer.sectionsNav': 'Секции формы',
'drawer.searchPlaceholder': 'Поиск полей',
'drawer.onlyFilled': 'только заполненные',
'scope.PUBLIC': 'PUBLIC — публичный',
'scope.INTERNAL': 'INTERNAL — внутренний',
'scope.RESTRICTED': 'RESTRICTED — ограниченный',
'schema.action.create': 'Создать',
'schema.action.edit': 'Изменить справочник',
'schema.action.editSchema': 'Схема',
'schema.tabs.metadata': 'Метаданные',
'schema.tabs.schema': 'Поля',
'schema.tabs.preview': 'JSON',
'schema.tabs.events': 'События',
'schema.redisProjection.label': 'Включить Redis-проекцию для этого справочника',
'schema.redisProjection.hint': 'CEO plan E2 (tiered perf). По умолчанию выключено — read-api идёт в PG read replica. Включай когда dict упирается в RPS limit (целевые 10k+ RPS read через Redis hot cache). Projection-writer материализует updates per locale в Redis. Read-api routing появится отдельным feature — пока flag готовит почву.',
'schema.events.intro': 'Превью JSON событий, которые будут опубликованы в Kafka после save справочника. Данные подставлены на основе типов полей schema. Полезно интеграторам (Альтум, Геопортал) для понимания shape\'а до live-выкатки.',
'schema.events.topic': 'Topic',
'schema.events.disclaimer': 'Это client-side preview. Реальные события содержат actual data + traceId/spanId из request scope. Backend events module — единственный source of truth.',
'schema.name': 'Имя',
'schema.nameHint': 'snake_case, начинается с буквы (например my_dictionary)',
'schema.nameInvalid': 'Только snake_case: a-z, 0-9, _; начинается с буквы',
'schema.displayName': 'Отображаемое имя',
'schema.description': 'Описание',
'schema.scope': 'Scope',
'schema.bundle': 'Bundle',
'schema.bundleHint': 'Обычно cuod',
'schema.template.label': 'Создать по шаблону',
'schema.template.hint': 'Скопирует схему, scope и локали выбранного словаря. Укажите уникальное имя — версия сбросится на 1.0.0.',
'schema.template.placeholder': '— без шаблона —',
'schema.version': 'Версия схемы',
'schema.supportedLocales': 'Поддерживаемые локали',
'schema.defaultLocale': 'Локаль по умолчанию',
'schema.property': 'Поле',
'schema.required': 'Обязательное',
'schema.pattern': 'Pattern (regex)',
'schema.enumValues': 'Значения enum (через запятую)',
'schema.enumValuesHint': 'Например: ACTIVE, INACTIVE, RETIRED',
'schema.arrayEnumOptional': 'Допустимые значения (опц., через запятую)',
'schema.unique': 'Уникальное',
'schema.uniqueHint': 'Дубликаты по этому полю запрещены',
'schema.idSource': 'Использовать как ID (бизнес-ключ)',
'schema.idSourceHint': 'Бизнес-ключ записи будет автоматически = значению этого поля',
'schema.idSourceManual': 'Ввод вручную',
'schema.versionSuggest': 'Рекомендуемая версия: {{v}}',
'schema.diff.blockedTitle': 'Несовместимое изменение схемы',
'schema.diff.blockedBody': 'Эти изменения сломают существующие записи. Сохранение заблокировано. Создайте новый справочник или откатите изменения.',
'schema.diff.compatibleTitle': 'Изменения схемы (совместимые)',
'history.title': 'История версий',
'history.empty': 'История пуста',
'history.current': 'текущая',
'history.active': 'активна',
'history.scheduled': 'запланирована',
'history.closed': 'закрыта',
'history.validFrom': 'действует с',
'history.validTo': 'действует до',
'history.updatedBy': 'кем',
'history.viewData': 'данные записи (JSON)',
'history.hideData': 'скрыть JSON',
'history.compare': 'Сравнить с текущим',
'history.revert': 'Откатить к v{{v}}',
'history.revertConfirm': 'Откатить к v{{v}}? Создастся новая активная версия.',
'history.singleVersionHint': 'Это единственная версия. Кнопки «Сравнить» и «Откатить» появятся, когда у записи будет более одной версии.',
// === Changelog (schema timeline + diff) ===
'changelog.empty.title': 'История пуста',
'changelog.empty.description': 'Изменения схемы и события workflow появятся здесь.',
'changelog.viewDiff': 'Сравнить',
'changelog.recordsAffected_one': '{{count}} запись',
'changelog.recordsAffected_few': '{{count}} записи',
'changelog.recordsAffected_many': '{{count}} записей',
'changelog.diff.title': 'Изменения схемы',
'changelog.diff.before': 'До',
'changelog.diff.after': 'После',
'changelog.diff.beforeEmpty': '— (первоначальное создание словаря)',
'changelog.diff.afterEmpty': '— (нет proposed schema для этого события)',
'changelog.diff.added': 'Добавлены',
'changelog.diff.removed': 'Удалены',
'changelog.diff.changed': 'Изменены',
'changelog.diff.by': 'автор',
'changelog.kind.definition_create': 'Создан словарь',
'changelog.kind.definition_update': 'Изменена схема',
'changelog.kind.draft_create': 'Черновик создан',
'changelog.kind.draft_update': 'Черновик обновлён',
'changelog.kind.draft_review': 'Отправлен на ревью',
'changelog.kind.draft_approve': 'Одобрен',
'changelog.kind.draft_reject': 'Отклонён',
'changelog.kind.draft_changes_requested': 'Запрошены правки',
'changelog.kind.draft_publish': 'Опубликован',
'changelog.kind.draft_withdraw': 'Отозван',
// === Workflow banner: schema-draft state ===
'workflow.schemaDraft.draft': 'Черновик схемы создан',
'workflow.schemaDraft.review_pending': 'Черновик схемы на ревью',
'workflow.schemaDraft.approved': 'Черновик схемы одобрен — готов к публикации',
'workflow.schemaDraft.changes_requested': 'По черновику запрошены правки',
'workflow.schemaDraft.rejected': 'Черновик схемы отклонён',
'workflow.schemaDraft.published': 'Черновик опубликован',
'workflow.schemaDraft.withdrawn': 'Черновик отозван',
'workflow.schemaDraft.subtitle': 'от v{{fromVersion}}',
'workflow.schemaDraft.open': 'Открыть черновик',
'workflow.schemaDraft.drawerTitle': 'Черновик схемы',
'workflow.schemaDraft.empty': 'Активного черновика схемы нет.',
'workflow.schemaDraft.terminal': 'terminal',
'workflow.schemaDraft.terminalHint': 'Терминальный статус — действия недоступны.',
'workflow.schemaDraft.maker': 'Автор',
'workflow.schemaDraft.from': 'От версии',
'workflow.schemaDraft.createdAt': 'Создан',
'workflow.schemaDraft.submittedAt': 'На ревью с',
'workflow.schemaDraft.decisionAt': 'Решение',
'workflow.schemaDraft.reason': 'Причина',
'workflow.schemaDraft.decisionComment': 'Комментарий',
'workflow.schemaDraft.proposed': 'Предложенная схема',
'workflow.schemaDraft.confirmWithdraw': 'Отозвать черновик? Действие нельзя отменить.',
'workflow.schemaDraft.statusBadge.draft': 'Черновик',
'workflow.schemaDraft.statusBadge.review_pending': 'На ревью',
'workflow.schemaDraft.statusBadge.approved': 'Одобрено',
'workflow.schemaDraft.statusBadge.changes_requested': 'Запрошены правки',
'workflow.schemaDraft.statusBadge.rejected': 'Отклонён',
'workflow.schemaDraft.statusBadge.published': 'Опубликован',
'workflow.schemaDraft.statusBadge.withdrawn': 'Отозван',
'workflow.schemaDraft.comment.decision': 'Комментарий ревьюера',
'workflow.schemaDraft.comment.publish': 'Release note',
'workflow.schemaDraft.comment.note': 'Комментарий автора',
'workflow.schemaDraft.comment.decisionPlaceholder': 'Краткое обоснование решения (опционально)',
'workflow.schemaDraft.comment.publishPlaceholder': 'Что меняется в этой версии (попадёт в changelog)',
'workflow.schemaDraft.comment.notePlaceholder': 'На что обратить внимание ревьюеру',
'workflow.schemaDraft.actions.submit': 'Отправить на ревью',
'workflow.schemaDraft.actions.resubmit': 'Доработать и отправить',
'workflow.schemaDraft.actions.approve': 'Одобрить',
'workflow.schemaDraft.actions.requestChanges': 'Запросить правки',
'workflow.schemaDraft.actions.reject': 'Отклонить',
'workflow.schemaDraft.actions.publish': 'Опубликовать',
'workflow.schemaDraft.actions.withdraw': 'Отозвать',
'workflow.schemaDraft.toast.submitted': 'Отправлено на ревью',
'workflow.schemaDraft.toast.approve': 'Черновик одобрен',
'workflow.schemaDraft.toast.request_changes': 'Запрошены правки',
'workflow.schemaDraft.toast.reject': 'Черновик отклонён',
'workflow.schemaDraft.toast.published': 'Опубликовано v{{version}}',
'workflow.schemaDraft.toast.withdrawn': 'Черновик отозван',
'workflow.schemaDraft.toast.submitFailed': 'Не удалось отправить на ревью',
'workflow.schemaDraft.toast.decideFailed': 'Не удалось зафиксировать решение',
'workflow.schemaDraft.toast.publishFailed': 'Не удалось опубликовать',
'workflow.schemaDraft.toast.withdrawFailed': 'Не удалось отозвать',
// === CreateSchemaDraftModal ===
'createDraft.title': 'Создать черновик схемы',
'createDraft.intro': 'Изменения сохранятся как черновик от текущей версии. Live-схема не меняется до публикации после ревью.',
'createDraft.openButton': 'Создать черновик схемы',
'createDraft.reason': 'Причина изменений',
'createDraft.reasonPlaceholder': 'Зачем эти изменения нужны (видно ревьюеру)',
'createDraft.schema': 'Схема (JSON)',
'createDraft.format': 'Format',
'createDraft.validate': 'Validate',
'createDraft.validationPassed': 'JSON валиден',
'createDraft.notObject': 'Schema должна быть JSON object',
'createDraft.submit': 'Создать черновик',
'createDraft.creating': 'Создание…',
'createDraft.toast.created': 'Черновик создан',
'createDraft.toast.failed': 'Не удалось создать черновик',
'createDraft.editTitle': 'Редактировать черновик',
'createDraft.editIntro': 'После сохранения статус черновика вернётся в draft — отправьте его на ревью заново.',
'createDraft.submitEdit': 'Сохранить',
'createDraft.updating': 'Сохранение…',
'createDraft.toast.updated': 'Черновик обновлён',
'createDraft.toast.updateFailed': 'Не удалось обновить черновик',
'workflow.schemaDraft.actions.edit': 'Редактировать',
// === Schema review queue panel ===
'reviews.tab.records': 'Записи',
'reviews.tab.schemas': 'Схемы',
'reviews.tab.mine': 'Мои',
// === Notifications (TODO 7) ===
'notifications.title': 'Уведомления',
'notifications.empty': 'Уведомлений пока нет',
'notifications.emptyDescription':
'Здесь будут уведомления о решениях по вашим черновикам и других событиях.',
'notifications.markAllRead': 'Прочитать все',
'notifications.markRead': 'Отметить прочитанным',
'notifications.read': 'Прочитано',
'notifications.unread': 'Непрочитано',
'notifications.allRead': 'Всё прочитано',
'notifications.unreadCount_one': '{{count}} непрочитанное',
'notifications.unreadCount_few': '{{count}} непрочитанных',
'notifications.unreadCount_many': '{{count}} непрочитанных',
'notifications.newDefault': 'Новое уведомление',
'notifications.toastDescription':
'Откройте колокольчик чтобы увидеть подробности.',
// Interaction state polish (TODO 2)
'reviews.emptyDescription':
'Очередь чистая. Прошлые решения можно посмотреть в журнале аудита.',
'reviews.emptyAuditLink': 'Открыть /audit',
'reviews.drawer.rejectReasonRequired':
'Укажите причину отклонения — comment обязателен для reject (compliance).',
'workflow.schemaDraft.emptyHint':
'Создайте новый черновик через editor словаря — кнопка «Создать черновик схемы» появится если ваши изменения требуют ревью.',
'reviews.schema.empty': 'Очередь пуста',
'reviews.schema.emptyDescription': 'Схемы на ревью появятся здесь.',
// === /reviews → "Мои" tab (maker self-tracking) ===
'reviews.mine.empty': 'У вас пока нет черновиков',
'reviews.mine.emptyDescription':
'Черновики появятся здесь, когда вы отправите изменения записи или схемы на ревью.',
'reviews.mine.emptyFilter': 'В этой категории черновиков нет',
'reviews.mine.anonTitle': 'Войдите чтобы видеть свои черновики',
'reviews.mine.anonDescription':
'Собственные drafts видны только после авторизации — backend фильтрует по вашему JWT sub claim. Войдите через Keycloak чтобы открыть Мои.',
// === /me/notifications/preferences (Phase B) ===
'prefs.title': 'Уведомления',
'prefs.description':
'Per-channel настройки доставки. Применяется только к вашим уведомлениям (reviewer pool inbox остаётся ON).',
'prefs.anonTitle': 'Войдите чтобы управлять подписками',
'prefs.anonDescription':
'Настройки уведомлений хранятся per-user (по JWT sub claim). Войдите через Keycloak чтобы открыть страницу.',
'prefs.email.label': 'Email',
'prefs.email.description':
'SMTP доставка через сконфигурированный mail.host. На staging это mailpit catcher; на prod — реальный relay.',
'prefs.express.label': 'Express',
'prefs.express.description':
'Корпоративный мессенджер. Канал ещё не подключён — toggle сохраняется на будущее.',
'prefs.telegram.label': 'Telegram',
'prefs.telegram.description':
'Telegram бот. Канал ещё не подключён — toggle сохраняется на будущее.',
'prefs.comingSoon': 'Скоро',
'prefs.saveError': 'Не удалось сохранить настройки. Попробуйте ещё раз.',
'prefs.quietHours.title': 'Тихие часы',
'prefs.quietHours.description':
'В этом окне email и express не отправляются. Внутренние уведомления в шапке остаются.',
'prefs.quietHours.toggle': 'Включить тихие часы',
'prefs.quietHours.start': 'Начало',
'prefs.quietHours.end': 'Конец',
'prefs.quietHours.tz': 'Часовой пояс',
'prefs.quietHours.save': 'Сохранить',
'prefs.quietHours.saving': 'Сохраняю…',
'prefs.quietHours.saved': 'Сохранено',
'prefs.quietHours.saveError': 'Не удалось сохранить тихие часы.',
'prefs.descriptionMatrix':
'Per-event настройки доставки. Reviewer pool inbox остаётся ON независимо от ваших настроек.',
'prefs.matrix.eventCol': 'Событие',
'prefs.event.RecordDraftSubmitted.label': 'Отправлено на ревью',
'prefs.event.RecordDraftSubmitted.description':
'Maker submit-нул draft. Reviewer pool ping о новом элементе в очереди.',
'prefs.event.RecordDraftApproved.label': 'Одобрено',
'prefs.event.RecordDraftApproved.description':
'Reviewer одобрил ваш draft — record live-нут.',
'prefs.event.RecordDraftRejected.label': 'Отклонено',
'prefs.event.RecordDraftRejected.description':
'Reviewer отклонил draft с комментарием — нужны правки.',
'prefs.event.RecordDraftWithdrawn.label': 'Отозвано',
'prefs.event.RecordDraftWithdrawn.description':
'Maker отозвал свой draft до решения. Pool ping чтобы вычистить очередь.',
'notifications.preferencesLink': 'Настроить каналы →',
'reviews.mine.filter.all': 'Все',
'reviews.mine.filter.pending': 'На ревью',
'reviews.mine.filter.wip': 'В работе',
'reviews.mine.filter.decided': 'Закрыты',
'reviews.mine.col.type': 'Тип',
'reviews.mine.col.target': 'Объект',
'reviews.mine.col.op': 'Операция',
'reviews.mine.col.status': 'Статус',
'reviews.mine.col.reviewer': 'Ревьюер',
'reviews.mine.col.submitted': 'Создан',
'reviews.mine.col.action': 'Действие',
'reviews.mine.type.record': 'Запись',
'reviews.mine.type.schema': 'Схема',
'reviews.mine.fromVersion': 'от v',
'reviews.mine.action.open': 'Открыть',
'reviews.mine.action.openSchema': 'Открыть',
'reviews.mine.recordStatus.PENDING': 'На ревью',
'reviews.mine.recordStatus.APPROVED': 'Одобрено',
'reviews.mine.recordStatus.REJECTED': 'Отклонён',
'reviews.mine.recordStatus.WITHDRAWN': 'Отозван',
// ReviewDrawer additions (read-only banner + own-pending withdraw flow)
'reviews.drawer.reviewer': 'Ревьюер',
'reviews.drawer.reviewedAt': 'Решение',
'reviews.drawer.reviewComment': 'Комментарий',
'reviews.drawer.ownPendingHint':
'Это ваш черновик на ревью. Approve/reject запрещены — можно отозвать.',
'reviews.action.close': 'Закрыть',
'reviews.action.withdraw': 'Отозвать',
'reviews.action.withdrawConfirm':
'Отозвать свой черновик? После этого он попадёт в Decided.',
'reviews.schema.title': 'Изменения схем',
'reviews.schema.label': 'Schema workflow',
'reviews.schema.queueTotal_one': '{{count}} draft ждёт ревью',
'reviews.schema.queueTotal_few': '{{count}} draft\'а ждут ревью',
'reviews.schema.queueTotal_many': '{{count}} draft\'ов ждут ревью',
'reviews.schema.dictionary': 'Словарь',
'reviews.schema.fromVersion': 'От версии',
'reviews.schema.maker': 'Автор',
'reviews.schema.submittedAt': 'Отправлен',
'reviews.schema.action': 'Действие',
'reviews.schema.open': 'Открыть',
// === Lineage (dict-relationships-v2 Phase 1) ===
'lineage.usedBy.title': 'На этот словарь ссылаются',
'lineage.usedBy.count_one': '{{count}} связь',
'lineage.usedBy.count_few': '{{count}} связи',
'lineage.usedBy.count_many': '{{count}} связей',
'lineage.usedBy.count_other': '{{count}} связей',
'lineage.usedBy.activeCount_one': '{{count}} запись',
'lineage.usedBy.activeCount_few': '{{count}} записи',
'lineage.usedBy.activeCount_many': '{{count}} записей',
'lineage.usedBy.activeCount_other': '{{count}} записей',
'lineage.usedBy.openSourceDict': 'Открыть словарь {{dict}}',
'lineage.refs.title': 'На эту запись ссылаются',
'lineage.refs.total_one': 'всего {{count}} ссылка',
'lineage.refs.total_few': 'всего {{count}} ссылки',
'lineage.refs.total_many': 'всего {{count}} ссылок',
'lineage.refs.total_other': 'всего {{count}} ссылок',
'lineage.refs.field': 'поле {{field}}',
'lineage.refs.created': 'создана {{when}}',
'lineage.refs.openSourceRecord': 'Открыть запись {{key}} в источнике',
'lineage.refs.stale': 'обновлено {{minutes}} мин назад',
'lineage.refs.staleHint': 'Данные из materialized view; обновляются раз в минуту. Закрытые между обновлениями записи отфильтрованы по valid_to.',
// === Time-travel (CEO plan v1 stretch) ===
'timeTravel.button': 'Time-travel…',
'timeTravel.label': 'Активная версия на момент',
'timeTravel.viewing': 'Просмотр на момент: {{date}}',
'timeTravel.clear': 'Сбросить',
'timeTravel.close': 'Закрыть',
'timeTravel.date': 'Дата',
'timeTravel.time': 'Время',
'timeTravel.hours': 'Часы',
'timeTravel.minutes': 'Минуты',
'timeTravel.quickPresets': 'Быстро:',
'timeTravel.preset.now': 'Сейчас',
'timeTravel.preset.hourAgo': '1 час',
'timeTravel.preset.dayAgo': 'Вчера',
'timeTravel.preset.weekAgo': '7 дней',
'timeTravel.preset.monthAgo': '30 дней',
'timeTravel.preset.yearAgo': '1 год',
'timeTravel.hint': 'Кликните или потяните точку на оси чтобы выбрать момент.',
// === Cascade close (Phase 3) ===
'cascade.title': 'Каскадное закрытие',
'cascade.summary.willClose_one': 'Будет закрыта {{total}} запись (включая «{{target}}»). Подтверждение требуется.',
'cascade.summary.willClose_few': 'Будут закрыты {{total}} записи (включая «{{target}}»). Подтверждение требуется.',
'cascade.summary.willClose_many': 'Будут закрыты {{total}} записей (включая «{{target}}»). Подтверждение требуется.',
'cascade.summary.willClose_other': 'Будут закрыты {{total}} записей (включая «{{target}}»). Подтверждение требуется.',
'cascade.summary.blocked_one': 'Закрытие «{{target}}» заблокировано: есть {{blockers}} активная зависимость.',
'cascade.summary.blocked_few': 'Закрытие «{{target}}» заблокировано: есть {{blockers}} активные зависимости.',
'cascade.summary.blocked_many': 'Закрытие «{{target}}» заблокировано: есть {{blockers}} активных зависимостей.',
'cascade.summary.blocked_other': 'Закрытие «{{target}}» заблокировано: есть {{blockers}} активных зависимостей.',
'cascade.blockers.title_one': '{{count}} запись блокирует закрытие',
'cascade.blockers.title_few': '{{count}} записи блокируют закрытие',
'cascade.blockers.title_many': '{{count}} записей блокируют закрытие',
'cascade.blockers.title_other': '{{count}} записей блокируют закрытие',
'cascade.blockers.more': 'и ещё {{count}}…',
'cascade.blockers.resolveFirst': 'Сначала закройте или замените эти ссылки, затем повторите попытку.',
'cascade.cascade.title_one': 'Будет каскадно закрыта {{count}} зависимая запись',
'cascade.cascade.title_few': 'Будут каскадно закрыты {{count}} зависимых записи',
'cascade.cascade.title_many': 'Будет каскадно закрыто {{count}} зависимых записей',
'cascade.cascade.title_other': 'Будет каскадно закрыто {{count}} зависимых записей',
'cascade.cascade.more': 'и ещё {{count}}…',
'cascade.warnings.title_one': '{{count}} запись останется без ссылки (warn-mode)',
'cascade.warnings.title_few': '{{count}} записи останутся без ссылки (warn-mode)',
'cascade.warnings.title_many': '{{count}} записей останутся без ссылки (warn-mode)',
'cascade.warnings.title_other': '{{count}} записей останутся без ссылки (warn-mode)',
'cascade.warnings.note': 'После закрытия они станут orphan и будут видны на orphan scanner. Можно поправить ссылки потом.',
'cascade.reason.label': 'Причина (необязательно)',
'cascade.reason.placeholder': 'Например: декомиссия станции SAR-X',
'cascade.confirmGate.label': 'Введите слово {{word}} для подтверждения',
'cascade.action.cancel': 'Отмена',
'cascade.action.confirm_one': 'Закрыть {{count}} запись',
'cascade.action.confirm_few': 'Закрыть {{count}} записи',
'cascade.action.confirm_many': 'Закрыть {{count}} записей',
'cascade.action.confirm_other': 'Закрыть {{count}} записей',
'lineage.onClose.BLOCK': 'block',
'lineage.onClose.WARN': 'warn',
'lineage.onClose.CASCADE': 'cascade',
'pagination.prev': 'Назад',
'pagination.next': 'Вперёд',
'pagination.pageOf': 'стр. {{page}} из {{total}}',
'schema.empty': 'Полей нет — добавь первое',
'schema.addProperty': 'Добавить поле',
'schema.delete': 'Удалить',
'schema.moveUp': 'Выше',
'schema.moveDown': 'Ниже',
'schema.kind.label': 'Тип',
'schema.kind.string': 'Строка',
'schema.kind.integer': 'Целое',
'schema.kind.number': 'Число',
'schema.kind.boolean': 'Логическое',
'schema.kind.enum': 'Enum (выбор из списка)',
'schema.kind.reference': 'Ссылка на словарь (FK)',
'schema.kind.localized': 'I18N строка (ru-RU/en-US/...)',
'schema.kind.date': 'Дата',
'schema.kind.datetime': 'Дата+время',
'schema.kind.array_string': 'Массив строк',
'schema.kind.opaque': 'Сложный (JSON)',
'schema.reference.title': 'FK на запись справочника',
'schema.reference.hint': 'Значение поля будет проверяться при сохранении: оно должно совпадать с business-key или указанным полем существующей записи целевого словаря.',
'schema.reference.targetDict': 'Целевой словарь',
'schema.reference.targetField': 'Поле',
'schema.reference.targetFieldHint': 'Обычно code или business_key',
'schema.reference.required': 'Заполните оба: словарь и поле — иначе FK не сработает.',
'schema.opaque.title': 'Сложное поле — редактируется только через JSON-таб',
'schema.opaque.hint': 'Nested object / array of objects. Поля-табе нет редактора, raw schema показан ниже. Меняй через вкладку "JSON".',
'field.name': 'Наименование',
'field.designator': 'Designator (COSPAR)',
'field.norad_id': 'NORAD ID',
'field.status': 'Статус',
'field.country': 'Страна',
'field.operator': 'Оператор',
'field.mass_kg': 'Масса (кг)',
'field.mission': 'Миссия',
'field.orbit': 'Орбита',
'field.launch_date': 'Дата запуска',
'field.decay_date': 'Дата схода',
'field.spectrum_bands': 'Спектральные диапазоны',
'field.satellite_type_code': 'Тип КА (код)',
'field.code': 'Код',
'field.description': 'Описание',
'field.location': 'Местоположение',
'field.coordinates': 'Координаты',
'field.frequency_bands': 'Частотные диапазоны',
'loading': 'Загрузка...',
'error.failed': 'Не удалось загрузить данные',
'auth.login': 'Войти',
'auth.logout': 'Выйти',
'dict.filter.searchPlaceholder': 'Поиск по записям…',
'dict.filter.scopeLabel': 'Фильтр по scope',
'dict.filter.matched': 'Найдено {{count}} из {{total}}',
'dict.filter.clear': 'Сбросить',
'dict.filter.noMatches': 'По фильтру записей не найдено',
'aoi.button': 'AOI фильтр',
'aoi.activeBbox': 'AOI: bbox {{value}}',
'aoi.activePolygon': 'AOI: полигон ({{points}} точек)',
'aoi.clear': 'Сбросить AOI',
'aoi.dialog.title': 'Выбор области интереса (AOI)',
'aoi.dialog.help': 'Нарисуйте прямоугольник или полигон. Применяется как фильтр к записям со spatial geometry.',
'aoi.dialog.apply': 'Применить',
'aoi.dialog.drawRectangle': 'Прямоугольник',
'aoi.dialog.drawPolygon': 'Полигон',
'aoi.dialog.clear': 'Очистить',
'aoi.dialog.polygonHint': 'Клик — добавить точку, двойной клик — закрыть полигон (минимум 3 точки)',
'aoi.dialog.mapAriaLabel': 'Карта для выбора области интереса',
'aoi.error.noShape': 'Сначала нарисуйте область на карте',
'aoi.error.invalid': 'Геометрия некорректна — попробуйте перерисовать',
},
},
'en-US': {
translation: {
'app.title': 'Ordinis MDM',
'theme.label': 'Theme',
'theme.light': 'Light',
'theme.dark': 'Dark',
'theme.system': 'System',
'nav.docs': 'Docs',
'nav.home': 'Home',
'nav.graph': 'Relations graph',
'nav.section.workflow': 'Workflow',
'nav.section.admin': 'Admin',
'sidebar.collapse': 'Collapse',
'sidebar.expand': 'Expand',
'topbar.search.label': 'Search',
'topbar.search.placeholder': 'Search dictionaries, ⌘K',
'topbar.notifications': 'Notifications',
'whatsnew.button': "What's new",
'whatsnew.title': "What's new in Ordinis",
'whatsnew.description': 'Latest releases. Updates land on your server with the next deploy.',
'whatsnew.unread_one': '{{count}} new',
'whatsnew.unread_other': '{{count}} new',
'whatsnew.newBadge': 'new',
'whatsnew.empty': 'No releases yet.',
'whatsnew.entryEmpty': 'No description.',
'whatsnew.footer': 'Full changelog — see CHANGELOG.md in repository.',
'graph.title': 'Dictionary relations graph',
'graph.description': 'Network diagram of all dictionaries and their FK links. Click a node for detail.',
'graph.comingSoon.title': 'Coming soon',
'graph.comingSoon.description': 'd3-force visualization of 40+ dictionaries with linked edges. WIP (Stage 3.3 redesign roadmap).',
'graph.search.placeholder': 'Find dictionary…',
'graph.search.empty': 'no matches',
'graph.search.count': '{{count}} of {{total}}',
'updateBanner.message': 'A new NSI version ({{version}}) is available. Reload to get the latest fixes.',
'updateBanner.messageGeneric': 'A new NSI version is available. Reload to get the latest fixes.',
'updateBanner.reload': 'Reload',
'updateBanner.docs': "What's new",
'updateBanner.dismiss': 'Dismiss notification',
'nav.dictionaries': 'Dictionaries',
'nav.audit': 'Audit',
'nav.outbox': 'Outbox',
'nav.webhooks': 'Webhooks',
'nav.search': 'Search',
'nav.reviews': 'Reviews',
'nav.myDrafts': 'My drafts',
'myDrafts.title': 'My drafts',
'myDrafts.description': 'All your submissions with history: pending, approved, rejected, withdrawn.',
'myDrafts.empty': "You haven't submitted any drafts yet.",
'myDrafts.total_one': '{{count}} draft',
'myDrafts.total_other': '{{count}} drafts',
'myDrafts.col.bk': 'Business key',
'myDrafts.col.op': 'Operation',
'myDrafts.col.status': 'Status',
'myDrafts.col.submitted': 'Submitted',
'myDrafts.col.reviewed': 'Reviewed',
'myDrafts.col.reviewer': 'Reviewer',
'myDrafts.col.comment': 'Comment',
'myDrafts.col.actions': 'Actions',
'myDrafts.action.withdraw': 'Withdraw',
'myDrafts.confirmWithdraw': 'Withdraw draft? This is irreversible — status becomes WITHDRAWN.',
'reviews.title': 'Review queue',
'reviews.description': 'Pending drafts from makers awaiting approve/reject. Pool model: first approver handles.',
'reviews.empty': 'No pending reviews. All clear 👍',
'reviews.queueTotal_one': '{{count}} pending draft',
'reviews.queueTotal_other': '{{count}} pending drafts',
'reviews.col.dict': 'Dictionary',
'reviews.col.bk': 'Business key',
'reviews.col.op': 'Operation',
'reviews.col.maker': 'Maker',
'reviews.col.submitted': 'Submitted',
'reviews.col.actions': 'Actions',
'reviews.action.review': 'Review',
'reviews.action.approve': 'Approve',
'reviews.action.reject': 'Reject',
'reviews.action.cancel': 'Cancel',
'reviews.bulk.selectAll': 'Select all',
'reviews.bulk.selectRow': 'Select draft',
'reviews.bulk.selectedCount_one': '{{count}} selected',
'reviews.bulk.selectedCount_other': '{{count}} selected',
'reviews.bulk.approveSelected': 'Approve selected',
'reviews.bulk.rejectSelected': 'Reject selected',
'reviews.bulk.clear': 'Clear',
'reviews.bulk.confirmApprove_one': 'Approve {{count}} draft? Each will produce a live-write.',
'reviews.bulk.confirmApprove_other': 'Approve {{count}} drafts? Each will produce a live-write.',
'reviews.bulk.rejectModalTitle_one': 'Reject {{count}} draft',
'reviews.bulk.rejectModalTitle_other': 'Reject {{count}} drafts',
'reviews.bulk.rejectModalDescription': 'All selected drafts will be rejected with one shared reason. Maker sees it in My drafts.',
'reviews.bulk.rejectReasonLabel': 'Reason (shared for all)',
'reviews.bulk.rejectReasonPlaceholder': 'e.g. duplicate registration, incomplete data, wrong scope...',
'reviews.bulk.confirmReject_one': 'Reject {{count}}',
'reviews.bulk.confirmReject_other': 'Reject {{count}}',
'reviews.bulk.cancel': 'Cancel',
'reviews.bulk.resultTitle': 'Bulk operation finished',
'reviews.bulk.approvedCount_one': 'Approved {{count}}',
'reviews.bulk.approvedCount_other': 'Approved {{count}}',
'reviews.bulk.rejectedCount_one': 'Rejected {{count}}',
'reviews.bulk.rejectedCount_other': 'Rejected {{count}}',
'reviews.bulk.failedCount_one': '{{count}} failed',
'reviews.bulk.failedCount_other': '{{count}} failed',
'reviews.drawer.title': 'Review draft',
'reviews.drawer.maker': 'maker',
'reviews.drawer.submitted': 'submitted',
'reviews.drawer.makerComment': 'Maker comment',
'reviews.drawer.live': 'Current (live)',
'reviews.drawer.proposed': 'Proposed',
'reviews.drawer.noLive': 'No live record (CREATE).',
'reviews.drawer.closeNote': 'CLOSE: record will be closed (valid_to=now).',
'reviews.drawer.comment': 'Reviewer comment',
'reviews.drawer.commentPlaceholder': 'Optional for approve, required for reject',
'schema.approvalRequired.label': 'Require approval (maker-checker)',
'schema.approvalRequired.hint': 'Approval Workflow v2. All CRUD goes through draft → review → approve. Protects critical dictionaries from operator error. Existing endpoints (POST/PUT/DELETE) return 409 draft_required pointing to /drafts API.',
'schema.approvalRequired.editorBlockTitle': 'Changes require review',
'schema.approvalRequired.editorBlock': 'This dictionary requires maker-checker review. Open a draft — your current edits will be pre-filled, you only need to add a reason and submit for review.',
'schema.approvalRequired.openDraftCta': 'Create draft with my changes',
'schema.approvalMinRole.label': 'Minimum reviewer role (optional)',
'schema.approvalMinRole.hint': 'e.g. ordinis:internal or ordinis:restricted. When set, backend requires reviewer to hold this role (on top of standard ordinis:record:reviewer / ordinis:schema:reviewer) for approve / reject / publish. Empty = standard reviewer role suffices. ordinis:admin always passes.',
'search.title': 'Smart search',
'search.description': 'Search across all dictionary content. Active records, current moment. Min 3 chars (trigram index).',
'search.placeholder': 'Type 3+ chars: code, name, identifier…',
'search.tooShort': 'Type at least 3 characters to run search.',
'search.empty': 'No results. Try a different query.',
'search.totalHits_one': '{{count}} record found',
'search.totalHits_other': '{{count}} records found',
'search.intro': 'Type a query and press Enter. Results grouped per dictionary; max 10 per dictionary, 100 total.',
'webhooks.title': 'Webhook subscriptions',
'webhooks.subtitle': 'HTTP delivery of events to non-Kafka consumers',
'webhooks.empty': 'No subscriptions yet. Create one via the button on the right.',
'webhooks.active': 'Active',
'webhooks.inactive': 'Disabled',
'webhooks.allScopes': 'all scopes',
'webhooks.allDicts': 'all dictionaries',
'webhooks.allEvents': 'all events',
'webhooks.notFound': 'Subscription not found',
'webhooks.col.name': 'Name',
'webhooks.col.url': 'URL',
'webhooks.col.scopes': 'Scope filter',
'webhooks.col.dictionaries': 'Dictionaries',
'webhooks.col.status': 'Status',
'webhooks.action.create': 'Create',
'webhooks.action.delete': 'Delete',
'webhooks.action.rotateSecret': 'Rotate secret',
'webhooks.action.test': 'Test ping',
'webhooks.test.success': 'Receiver got the test event and replied 2xx',
'webhooks.test.failure': 'Receiver rejected the test event',
'webhooks.test.rejected': 'Request not sent (SSRF guard / serialization)',
'webhooks.test.latency': 'latency',
'webhooks.confirmDelete': 'Delete subscription "{{name}}"? All pending deliveries will be cascade-deleted.',
'webhooks.confirmRotate': 'Rotate HMAC secret? The old one stops working immediately — update the receiver first.',
'webhooks.field.name': 'Subscription name',
'webhooks.field.url': 'Webhook URL',
'webhooks.field.urlHint': 'https:// or http://. Private CIDRs (10/8, 192.168/16, 127/8) blocked by SSRF guard',
'webhooks.field.scopeFilter': 'Scope filter',
'webhooks.field.scopeFilterHint': 'Empty = all scopes',
'webhooks.field.dictionaryFilter': 'Dictionaries (CSV)',
'webhooks.field.dictionaryFilterHint': 'Comma-separated: spacecraft, satellite_type. Empty = all',
'webhooks.field.eventTypeFilter': 'Event types',
'webhooks.field.eventTypeFilterHint': 'Record*, Definition*, SchemaDraft*. Empty = all events.',
'webhooks.field.eventTypeSearch': 'Search event type',
'webhooks.field.description': 'Description',
'webhooks.field.createdBy': 'Created by',
'webhooks.error.nameUrlRequired': 'Name and URL are required',
'webhooks.error.urlScheme': 'URL must start with http:// or https://',
'webhooks.secret.title': 'HMAC secret for "{{name}}"',
'webhooks.secret.warningTitle': 'Copy now!',
'webhooks.secret.warning': 'This secret is shown ONCE. It will be masked afterwards. Pass it to the receiver so it can verify the X-Ordinis-Signature header.',
'webhooks.secret.rotatedTitle': 'Secret rotated',
'webhooks.secret.rotatedWarning': 'New secret. Old one stopped working. Update the receiver.',
'webhooks.secret.copy': 'Copy',
'webhooks.secret.copied': 'Copied',
'webhooks.secret.acknowledge': 'Saved it',
'webhooks.deliveries.title': 'Delivery history',
'webhooks.deliveries.empty': 'No deliveries yet',
'webhooks.stats.pending': 'Pending',
'webhooks.stats.retrying': 'Retrying',
'webhooks.stats.delivered': 'Delivered',
'webhooks.stats.dlq': 'DLQ',
'webhooks.stats.successRate': 'Success rate',
'webhooks.stats.successRateOf_one': 'of {{count}} terminal',
'webhooks.stats.successRateOf_other': 'of {{count}} terminal',
'webhooks.status.pending': 'pending',
'webhooks.status.retrying': 'retrying',
'webhooks.status.delivered': 'delivered',
'webhooks.status.dlq': 'DLQ',
'webhooks.histogram.title': 'Delivery history',
'webhooks.histogram.bucket.hour': '24h',
'webhooks.histogram.bucket.day': '7d',
'webhooks.histogram.empty': 'No deliveries in the selected period.',
'webhooks.histogram.total': 'total',
// === Friendly query error states ===
'queryError.retry': 'Retry',
'queryError.network.title': 'No connection to server',
'queryError.network.body': 'Check your network. Once connected, retry.',
'queryError.unavailable.title': 'Server temporarily unavailable',
'queryError.unavailable.body': 'Possibly an update or scheduled maintenance. Wait a few seconds and retry.',
'queryError.server.title': 'Internal server error',
'queryError.server.body': 'Something went wrong on the server side. If it keeps happening, please contact the admin.',
'queryError.notFound.title': 'Not found',
'queryError.notFound.body': 'The requested data does not exist or has been removed.',
'queryError.forbidden.title': 'Access denied',
'queryError.forbidden.body': 'You do not have permission to view this data.',
'queryError.unauthorized.title': 'Login required',
'queryError.unauthorized.body': 'Session expired. Sign in again to continue.',
'queryError.client.title': 'Request rejected',
'queryError.client.body': 'The server rejected the request.',
'queryError.generic.title': 'Failed to load data',
'queryError.generic.body': 'Something went wrong. Please try again.',
'timeTravel.empty.noEvents': 'Nothing changed between these moments.',
'timeTravel.action.diff': 'Diff',
'timeTravel.records.empty': 'No records existed on {{date}}.',
'timeTravel.records.title_one': '{{count}} record on {{date}}',
'timeTravel.records.title_other': '{{count}} records on {{date}}',
'timeTravel.records.col.name': 'Name',
'timeTravel.records.col.validFrom': 'Valid from',
'timeTravel.records.truncated': 'Showing {{visible}} of {{total}}. Open as readonly for the full list.',
'timeTravel.schema.fieldsTitle': 'Dictionary fields at v{{version}}',
'timeTravel.schema.empty': 'No fields.',
'timeTravel.schema.fallback': 'Live schema shown — historical diff unavailable for this version.',
'timeTravel.schema.historyTitle': 'History of this version',
'timeTravel.schema.author': 'author',
'timeTravel.schema.date': 'date',
'timeTravel.schema.noHistory': 'History unavailable for the picked moment.',
'webhooks.deliveries.col.eventId': 'Event ID',
'webhooks.deliveries.col.attempts': 'Attempts',
'webhooks.deliveries.col.lastAttempt': 'Last attempt',
'webhooks.deliveries.col.lastStatusCode': 'HTTP status',
'webhooks.deliveries.col.lastError': 'Last error',
'webhooks.deliveries.action.retry': 'Retry delivery',
'webhooks.deliveries.confirmRetry': 'Reset attemptCount=0 and queue a retry? The receiver will get the payload again.',
'webhooks.deliveries.filter.label': 'Status filter',
'webhooks.deliveries.filter.clear': 'Clear',
'audit.title': 'Audit log',
'audit.subtitle': 'Legally significant log of NSI changes',
'audit.filter.dictionary': 'Dictionary',
'audit.filter.user': 'User',
'audit.filter.action': 'Action',
'audit.filter.businessKey': 'Business key',
'audit.filter.from': 'From',
'audit.filter.to': 'To',
'audit.filter.range': 'Date range',
'audit.filter.reset': 'Reset',
'audit.filter.apply': 'Apply',
'audit.filter.active': 'Active filters',
'audit.filter.removeChip': 'Remove filter',
'audit.col.time': 'Time',
'audit.col.action': 'Action',
'audit.col.dictionary': 'Dictionary',
'audit.col.businessKey': 'Business key',
'audit.col.user': 'User',
'audit.col.scope': 'Scope',
'audit.col.trace': 'Trace',
'audit.col.diff': 'Diff',
'audit.action.expand': 'Expand',
'audit.action.export': 'Export CSV',
'audit.empty': 'No entries match the filters',
'audit.action.CREATE': 'Record created',
'audit.action.UPDATE': 'Record updated',
'audit.action.CLOSE': 'Record closed',
'audit.action.DEFINITION_CREATE': 'Dictionary created',
'audit.action.DEFINITION_UPDATE': 'Schema updated',
'audit.action.DRAFT_CREATE': 'Draft created',
'audit.action.DRAFT_SUBMIT': 'Draft submitted',
'audit.action.DRAFT_REVIEW': 'Draft in queue',
'audit.action.DRAFT_APPROVE': 'Draft approved',
'audit.action.DRAFT_REJECT': 'Draft rejected',
'audit.action.DRAFT_CHANGES_REQUESTED': 'Changes requested',
'audit.action.DRAFT_WITHDRAW': 'Draft withdrawn',
'audit.action.DRAFT_PUBLISH': 'Draft published',
'audit.diff.before': 'Before',
'audit.diff.after': 'After',
'audit.page.of': 'Page {{cur}} of {{total}}',
'audit.page.size': 'Per page',
'audit.page.prev': 'Prev',
'audit.page.next': 'Next',
'outbox.title': 'Outbox events',
'outbox.subtitle': 'Publication queue and DLQ',
'outbox.stats.pending': 'Pending',
'outbox.stats.dlq': 'In DLQ',
'outbox.dlq.col.id': 'ID',
'outbox.dlq.col.eventType': 'Type',
'outbox.dlq.col.dictionary': 'Dictionary',
'outbox.dlq.col.aggregateId': 'Aggregate ID',
'outbox.dlq.col.topic': 'Topic',
'outbox.dlq.col.attempts': 'Attempts',
'outbox.dlq.col.lastError': 'Last error',
'outbox.dlq.col.dlqAt': 'DLQ since',
'outbox.dlq.empty': 'DLQ empty — all events publish successfully',
'header.scope': 'Allowed scope',
'dict.list.subtitle': 'Reference data catalogues',
'dict.list.search.placeholder': 'Search by name, code or description',
'dict.list.search.empty': 'Nothing found',
'dict.list.found': 'Showing {{shown}} of {{total}}',
'dict.list.recordCount_one': '{{count}} record',
'dict.list.recordCount_other': '{{count}} records',
'dict.list.section.PUBLIC': 'Public',
'dict.list.section.INTERNAL': 'Internal',
'dict.list.section.RESTRICTED': 'Restricted',
'dict.list.section.PUBLIC.short': 'PUB',
'dict.list.section.INTERNAL.short': 'INT',
'dict.list.section.RESTRICTED.short': 'RES',
'dict.list.deps.short': 'WITH LINKS',
// Catalog v2
'dict.list.heading': 'Dictionary catalog',
'dict.list.filter.scope': 'Scope filter (multi-select)',
'dict.list.sort.label': 'Sort',
'dict.list.sort.name': 'By name',
'dict.list.sort.recordCount': 'By record count',
'dict.list.sort.updated': 'By updated',
'dict.list.density.label': 'Density',
'dict.list.density.card': 'Cards',
'dict.list.density.dense': 'List',
'dict.list.search.reset': 'Reset filters',
'dict.list.approval': 'approval',
'dict.list.col.name': 'Name',
'dict.list.col.bundle': 'Bundle',
'dict.list.col.version': 'Version',
'dict.list.col.locales': 'Locales',
'dict.list.col.records': 'Records',
// Compact row table (catalog) — column headers EN.
'dict.col.title': 'Name',
'dict.col.id': 'id',
'dict.col.bundle': 'bundle',
'dict.col.records': 'records',
'dict.col.updated': 'updated',
'dict.col.outgoingFk': 'References (outgoing FK)',
'dict.col.incomingFk': 'Used by (incoming FK)',
'dict.list.graph': 'Graph',
// List view (handoff D)
'dict.list.bundle.all': 'all',
'dict.list.deps.only': 'Only with links',
'dict.list.records.short': 'dicts',
'dict.list.fk.references': '→ references',
'dict.list.fk.usedBy_one': '← {{count}} used by',
'dict.list.fk.usedBy_other': '← {{count}} used by',
'dict.empty': 'No records in this dictionary yet',
'dict.scheduled.hint_one':
'{{count}} record scheduled for {{date}}',
'dict.scheduled.hint_other':
'{{count}} records scheduled, earliest on {{date}}',
'dict.scheduled.cta': 'Jump to that date',
'dict.scheduled.row.badge': 'Scheduled',
'dict.scheduled.row.tooltip': 'This record has an upcoming scheduled version',
'aiSuggest.openButton': 'AI: add field',
'aiSuggest.title': 'AI: add field',
'aiSuggest.placeholder': 'Describe the field — e.g. "orbit: apogee, perigee, inclination"',
'aiSuggest.hint': 'AI suggests structure (type/format/x-references). Add GOST codes and validations manually.',
'aiSuggest.suggest': 'Suggest',
'aiSuggest.thinking': 'Thinking…',
'aiSuggest.preview': 'Suggestion',
'aiSuggest.retry': 'Regenerate',
'aiSuggest.accept': 'Add to schema',
'aiSuggest.error.circuit': 'AI temporarily unavailable. Try again in a few minutes.',
'aiSuggest.error.rate': 'Too many requests. Wait a minute.',
'aiSuggest.error.bad_output': 'AI returned invalid response. Try rephrasing.',
'aiSuggest.error.upstream': 'LLM endpoint unreachable. Wait or try later.',
'aiSuggest.error.disabled': 'AI is disabled in this installation.',
'aiSuggest.error.generic': 'Failed to get suggestion.',
'schemaTemplates.label': 'Start from template',
'schemaTemplates.hint': 'Template loads initial fields. Extend manually.',
'common.close': 'Close',
'dict.col.businessKey': 'Business key',
'dict.col.scope': 'Scope',
'dict.col.validFrom': 'Valid from',
'dict.col.locale': 'Locale',
'dict.col.actions': 'Actions',
'dict.pendingReview.label': 'Pending review',
'dict.pendingReview.tooltip': 'A draft is pending for this record. Open /reviews to approve/reject.',
'dict.list.records': 'records',
'dict.action.create': 'Create',
'dict.action.edit': 'Edit',
'dict.action.close': 'Close',
'dict.confirmClose.title': 'Close record?',
'dict.confirmClose.body':
'Record "{{key}}" will be marked as closed (valid_to = now). Reversible by creating a new version.',
'dict.confirmClose.reason': 'Reason (optional)',
'dict.confirmClose.reasonPlaceholder': 'e.g., "duplicate" or "decommissioned"',
'dict.bulk.selectAll': 'Select all on page',
'dict.bulk.selectRow': 'Select record',
'dict.bulk.selectedCount_one': '{{count}} of {{total}} selected',
'dict.bulk.selectedCount_other': '{{count}} of {{total}} selected',
'dict.bulk.clearSelection': 'Clear selection',
'dict.bulk.closeAction': 'Close selected',
'dict.bulk.exportAction': 'Export CSV',
'dict.page.prev': 'Prev',
'dict.page.next': 'Next',
'dict.page.of': 'Page {{cur}} of {{total}}',
'dict.page.range': 'Records {{from}}{{to}} of {{total}}',
'dict.bulk.confirmTitle': 'Bulk close records',
'dict.bulk.confirmBody':
'{{count}} record(s) will be marked as closed (valid_to = now). Reversible by creating a new version. Each record is closed individually — if some are already closed, others will still be processed.',
'dict.bulk.confirmAction': 'Close {{count}}',
'dict.bulk.resultClosed': 'Closed: {{count}}',
'dict.bulk.resultSkipped': 'Skipped (already closed or not found): {{count}}',
'dict.bulk.resultErrors': 'Errors: {{count}}',
'dict.bulk.dismiss': 'Done',
'form.identity': 'Identity',
'form.idSourceNote': 'Business key auto-derived from "{{field}}" field',
'form.tabs.identity': 'Identity',
'form.tabs.description': 'Description',
'form.tabs.extra': 'Additional',
'form.tabs.references': 'References',
'form.tabs.dates': 'Dates',
'form.tabs.geometry': 'Geometry',
'form.tabs.physical': 'Physical parameters',
'form.businessKey': 'Business key',
'form.validFrom': 'Valid from',
'form.validFromHint': 'When this record becomes active. Empty = from creation moment.',
'form.validTo': 'Valid to',
'form.validToHint': 'When it stops being valid. Empty = forever.',
'form.error.validToBeforeFrom': 'Valid-to must be later than valid-from.',
'form.dataFields': 'Record fields',
'form.cancel': 'Cancel',
'form.save': 'Save',
'form.saving': 'Saving...',
'form.delete': 'Close record',
'form.unsavedNFields_one': 'unsaved · {{count}} field',
'form.unsavedNFields_other': 'unsaved · {{count}} fields',
'drawer.collapse': 'Collapse to 520px',
'drawer.expand': 'Expand to 880px',
'drawer.close': 'Close',
'drawer.captionEdit': 'edit record · {{dict}}',
'drawer.captionCreate': 'new record · {{dict}}',
'drawer.sectionsNav': 'Form sections',
'drawer.searchPlaceholder': 'Search fields',
'drawer.onlyFilled': 'only filled',
'form.error.required': 'Required field',
'scope.PUBLIC': 'PUBLIC',
'scope.INTERNAL': 'INTERNAL',
'scope.RESTRICTED': 'RESTRICTED',
'schema.action.create': 'Create',
'schema.action.edit': 'Edit dictionary',
'schema.action.editSchema': 'Schema',
'schema.tabs.metadata': 'Metadata',
'schema.tabs.schema': 'Fields',
'schema.tabs.preview': 'JSON',
'schema.tabs.events': 'Events',
'schema.redisProjection.label': 'Enable Redis projection for this dictionary',
'schema.redisProjection.hint': 'CEO plan E2 (tiered perf). Off by default — read-api hits PG read replica. Turn on when this dict approaches RPS limit (target 10k+ RPS read through Redis hot cache). The projection writer materializes per-locale updates to Redis. Read-api routing will arrive in a separate feature; the flag prepares the ground.',
'schema.events.intro': 'Preview of JSON events that will be published to Kafka after the dictionary is saved. Data is filled in based on schema field types. Useful for integrators (Altum, Geoportal) to understand the shape before going live.',
'schema.events.topic': 'Topic',
'schema.events.disclaimer': 'This is a client-side preview. Real events contain actual data + traceId/spanId from request scope. The backend events module is the single source of truth.',
'schema.name': 'Name',
'schema.nameHint': 'snake_case, starts with letter (e.g. my_dictionary)',
'schema.nameInvalid': 'Only snake_case: a-z, 0-9, _; must start with letter',
'schema.displayName': 'Display name',
'schema.description': 'Description',
'schema.scope': 'Scope',
'schema.bundle': 'Bundle',
'schema.bundleHint': 'Usually cuod',
'schema.template.label': 'Create from template',
'schema.template.hint': 'Copies schema, scope and locales from the selected dictionary. Use a unique name; version resets to 1.0.0.',
'schema.template.placeholder': '— no template —',
'schema.version': 'Schema version',
'schema.supportedLocales': 'Supported locales',
'schema.defaultLocale': 'Default locale',
'schema.property': 'Field',
'schema.required': 'Required',
'schema.pattern': 'Pattern (regex)',
'schema.enumValues': 'Enum values (comma-separated)',
'schema.enumValuesHint': 'E.g.: ACTIVE, INACTIVE, RETIRED',
'schema.arrayEnumOptional': 'Allowed values (optional, comma-separated)',
'schema.unique': 'Unique',
'schema.uniqueHint': 'Duplicates by this field are not allowed',
'schema.idSource': 'Use as ID (business key)',
'schema.idSourceHint': 'Record business key auto-derived from this field value',
'schema.idSourceManual': 'Manual input',
'schema.versionSuggest': 'Suggested version: {{v}}',
'schema.diff.blockedTitle': 'Breaking schema change',
'schema.diff.blockedBody': 'These changes break existing records. Save blocked. Create a new dictionary or revert.',
'schema.diff.compatibleTitle': 'Schema changes (compatible)',
'history.title': 'Version history',
'history.empty': 'No history',
'history.current': 'current',
'history.active': 'active',
'history.scheduled': 'scheduled',
'history.closed': 'closed',
'history.validFrom': 'valid from',
'history.validTo': 'valid to',
'history.updatedBy': 'by',
'history.viewData': 'record data (JSON)',
'history.hideData': 'hide JSON',
'history.compare': 'Compare with current',
'history.revert': 'Revert to v{{v}}',
'history.revertConfirm': 'Revert to v{{v}}? A new active version will be created.',
'history.singleVersionHint': 'This is the only version. Compare and Revert actions will appear once the record has more than one version.',
// === Changelog (schema timeline + diff) ===
'changelog.empty.title': 'No history yet',
'changelog.empty.description': 'Schema changes and workflow events will appear here.',
'changelog.viewDiff': 'Compare',
'changelog.recordsAffected_one': '{{count}} record',
'changelog.recordsAffected_other': '{{count}} records',
'changelog.diff.title': 'Schema changes',
'changelog.diff.before': 'Before',
'changelog.diff.after': 'After',
'changelog.diff.beforeEmpty': '— (dictionary creation, no prior state)',
'changelog.diff.afterEmpty': '— (no proposed schema for this event)',
'changelog.diff.added': 'Added',
'changelog.diff.removed': 'Removed',
'changelog.diff.changed': 'Changed',
'changelog.diff.by': 'by',
'changelog.kind.definition_create': 'Dictionary created',
'changelog.kind.definition_update': 'Schema updated',
'changelog.kind.draft_create': 'Draft created',
'changelog.kind.draft_update': 'Draft updated',
'changelog.kind.draft_review': 'Sent for review',
'changelog.kind.draft_approve': 'Approved',
'changelog.kind.draft_reject': 'Rejected',
'changelog.kind.draft_changes_requested': 'Changes requested',
'changelog.kind.draft_publish': 'Published',
'changelog.kind.draft_withdraw': 'Withdrawn',
// === Workflow banner: schema-draft state ===
'workflow.schemaDraft.draft': 'Schema draft created',
'workflow.schemaDraft.review_pending': 'Schema draft under review',
'workflow.schemaDraft.approved': 'Schema draft approved — ready to publish',
'workflow.schemaDraft.changes_requested': 'Changes requested on draft',
'workflow.schemaDraft.rejected': 'Schema draft rejected',
'workflow.schemaDraft.published': 'Schema draft published',
'workflow.schemaDraft.withdrawn': 'Schema draft withdrawn',
'workflow.schemaDraft.subtitle': 'from v{{fromVersion}}',
'workflow.schemaDraft.open': 'Open draft',
'workflow.schemaDraft.drawerTitle': 'Schema draft',
'workflow.schemaDraft.empty': 'No active schema draft.',
'workflow.schemaDraft.terminal': 'terminal',
'workflow.schemaDraft.terminalHint': 'Terminal status — no actions available.',
'workflow.schemaDraft.maker': 'Author',
'workflow.schemaDraft.from': 'From version',
'workflow.schemaDraft.createdAt': 'Created',
'workflow.schemaDraft.submittedAt': 'Submitted',
'workflow.schemaDraft.decisionAt': 'Decided',
'workflow.schemaDraft.reason': 'Reason',
'workflow.schemaDraft.decisionComment': 'Comment',
'workflow.schemaDraft.proposed': 'Proposed schema',
'workflow.schemaDraft.confirmWithdraw': 'Withdraw draft? This action is irreversible.',
'workflow.schemaDraft.statusBadge.draft': 'Draft',
'workflow.schemaDraft.statusBadge.review_pending': 'Review',
'workflow.schemaDraft.statusBadge.approved': 'Approved',
'workflow.schemaDraft.statusBadge.changes_requested': 'Changes requested',
'workflow.schemaDraft.statusBadge.rejected': 'Rejected',
'workflow.schemaDraft.statusBadge.published': 'Published',
'workflow.schemaDraft.statusBadge.withdrawn': 'Withdrawn',
'workflow.schemaDraft.comment.decision': 'Reviewer comment',
'workflow.schemaDraft.comment.publish': 'Release note',
'workflow.schemaDraft.comment.note': 'Author note',
'workflow.schemaDraft.comment.decisionPlaceholder': 'Brief decision rationale (optional)',
'workflow.schemaDraft.comment.publishPlaceholder': 'What this version changes (lands in changelog)',
'workflow.schemaDraft.comment.notePlaceholder': 'What to highlight for the reviewer',
'workflow.schemaDraft.actions.submit': 'Submit for review',
'workflow.schemaDraft.actions.resubmit': 'Revise & resubmit',
'workflow.schemaDraft.actions.approve': 'Approve',
'workflow.schemaDraft.actions.requestChanges': 'Request changes',
'workflow.schemaDraft.actions.reject': 'Reject',
'workflow.schemaDraft.actions.publish': 'Publish',
'workflow.schemaDraft.actions.withdraw': 'Withdraw',
'workflow.schemaDraft.toast.submitted': 'Submitted for review',
'workflow.schemaDraft.toast.approve': 'Draft approved',
'workflow.schemaDraft.toast.request_changes': 'Changes requested',
'workflow.schemaDraft.toast.reject': 'Draft rejected',
'workflow.schemaDraft.toast.published': 'Published v{{version}}',
'workflow.schemaDraft.toast.withdrawn': 'Draft withdrawn',
'workflow.schemaDraft.toast.submitFailed': 'Failed to submit for review',
'workflow.schemaDraft.toast.decideFailed': 'Failed to record decision',
'workflow.schemaDraft.toast.publishFailed': 'Failed to publish',
'workflow.schemaDraft.toast.withdrawFailed': 'Failed to withdraw',
// === CreateSchemaDraftModal ===
'createDraft.title': 'Create schema draft',
'createDraft.intro': 'Changes are saved as a draft branched from the current version. Live schema is not touched until publish-after-review.',
'createDraft.openButton': 'Create schema draft',
'createDraft.reason': 'Reason for changes',
'createDraft.reasonPlaceholder': 'Why these changes are needed (visible to the reviewer)',
'createDraft.schema': 'Schema (JSON)',
'createDraft.format': 'Format',
'createDraft.validate': 'Validate',
'createDraft.validationPassed': 'JSON is valid',
'createDraft.notObject': 'Schema must be a JSON object',
'createDraft.submit': 'Create draft',
'createDraft.creating': 'Creating…',
'createDraft.toast.created': 'Draft created',
'createDraft.toast.failed': 'Failed to create draft',
'createDraft.editTitle': 'Edit draft',
'createDraft.editIntro': 'After save, the draft status reverts to draft — re-submit for review explicitly.',
'createDraft.submitEdit': 'Save',
'createDraft.updating': 'Saving…',
'createDraft.toast.updated': 'Draft updated',
'createDraft.toast.updateFailed': 'Failed to update draft',
'workflow.schemaDraft.actions.edit': 'Edit',
// === Schema review queue panel ===
'reviews.tab.records': 'Records',
'reviews.tab.schemas': 'Schemas',
'reviews.tab.mine': 'Mine',
// === Notifications (TODO 7) ===
'notifications.title': 'Notifications',
'notifications.empty': 'No notifications yet',
'notifications.emptyDescription':
'Decisions on your drafts and other events will appear here.',
'notifications.markAllRead': 'Mark all read',
'notifications.markRead': 'Mark read',
'notifications.read': 'Read',
'notifications.unread': 'Unread',
'notifications.allRead': 'All read',
'notifications.unreadCount_one': '{{count}} unread',
'notifications.unreadCount_other': '{{count}} unread',
'notifications.newDefault': 'New notification',
'notifications.toastDescription': 'Open the bell to see details.',
// Interaction state polish (TODO 2)
'reviews.emptyDescription':
'Queue is clear. Past decisions live in the audit log.',
'reviews.emptyAuditLink': 'Open /audit',
'reviews.drawer.rejectReasonRequired':
'Reject requires a comment (compliance) — fill the comment field first.',
'workflow.schemaDraft.emptyHint':
'Create a new draft via the dictionary editor — the "Create schema draft" button appears when your changes need review.',
'reviews.schema.empty': 'Queue is empty',
'reviews.schema.emptyDescription': 'Schemas under review will appear here.',
// === /reviews → "Mine" tab (maker self-tracking) ===
'reviews.mine.empty': 'You have no drafts yet',
'reviews.mine.emptyDescription':
'Drafts will appear here when you submit record or schema changes for review.',
'reviews.mine.anonTitle': 'Sign in to see your drafts',
'reviews.mine.anonDescription':
'Your own drafts are visible only after sign-in — backend filters by your JWT sub claim. Sign in via Keycloak to open Mine.',
// === /me/notifications/preferences (Phase B) ===
'prefs.title': 'Notifications',
'prefs.description':
'Per-channel delivery preferences. Applies only to your notifications (reviewer pool inbox stays ON).',
'prefs.anonTitle': 'Sign in to manage your subscriptions',
'prefs.anonDescription':
'Notification preferences are stored per-user (by JWT sub claim). Sign in via Keycloak to open this page.',
'prefs.email.label': 'Email',
'prefs.email.description':
'SMTP delivery via configured mail.host. On staging it is the mailpit catcher; on prod — real relay.',
'prefs.express.label': 'Express',
'prefs.express.description':
'Corporate messenger. Channel not wired yet — toggle is stored for later.',
'prefs.telegram.label': 'Telegram',
'prefs.telegram.description':
'Telegram bot. Channel not wired yet — toggle is stored for later.',
'prefs.comingSoon': 'Soon',
'prefs.saveError': 'Failed to save preferences. Please try again.',
'prefs.quietHours.title': 'Quiet hours',
'prefs.quietHours.description':
'Email and express muted during this window. In-app notifications remain visible.',
'prefs.quietHours.toggle': 'Enable quiet hours',
'prefs.quietHours.start': 'From',
'prefs.quietHours.end': 'To',
'prefs.quietHours.tz': 'Timezone',
'prefs.quietHours.save': 'Save',
'prefs.quietHours.saving': 'Saving…',
'prefs.quietHours.saved': 'Saved',
'prefs.quietHours.saveError': 'Failed to save quiet hours.',
'prefs.descriptionMatrix':
'Per-event delivery preferences. Reviewer pool inbox stays ON regardless of your settings.',
'prefs.matrix.eventCol': 'Event',
'prefs.event.RecordDraftSubmitted.label': 'Submitted for review',
'prefs.event.RecordDraftSubmitted.description':
'Maker submitted a draft. Reviewer pool ping about new queue item.',
'prefs.event.RecordDraftApproved.label': 'Approved',
'prefs.event.RecordDraftApproved.description':
'Reviewer approved your draft — record went live.',
'prefs.event.RecordDraftRejected.label': 'Rejected',
'prefs.event.RecordDraftRejected.description':
'Reviewer rejected the draft with a comment — needs fixes.',
'prefs.event.RecordDraftWithdrawn.label': 'Withdrawn',
'prefs.event.RecordDraftWithdrawn.description':
'Maker withdrew their draft before decision. Pool ping for queue cleanup.',
'notifications.preferencesLink': 'Manage channels →',
'reviews.mine.emptyFilter': 'No drafts in this category',
'reviews.mine.filter.all': 'All',
'reviews.mine.filter.pending': 'Under review',
'reviews.mine.filter.wip': 'In progress',
'reviews.mine.filter.decided': 'Closed',
'reviews.mine.col.type': 'Type',
'reviews.mine.col.target': 'Target',
'reviews.mine.col.op': 'Op',
'reviews.mine.col.status': 'Status',
'reviews.mine.col.reviewer': 'Reviewer',
'reviews.mine.col.submitted': 'Created',
'reviews.mine.col.action': 'Action',
'reviews.mine.type.record': 'Record',
'reviews.mine.type.schema': 'Schema',
'reviews.mine.fromVersion': 'from v',
'reviews.mine.action.open': 'Open',
'reviews.mine.action.openSchema': 'Open',
'reviews.mine.recordStatus.PENDING': 'Under review',
'reviews.mine.recordStatus.APPROVED': 'Approved',
'reviews.mine.recordStatus.REJECTED': 'Rejected',
'reviews.mine.recordStatus.WITHDRAWN': 'Withdrawn',
// ReviewDrawer additions (read-only banner + own-pending withdraw flow)
'reviews.drawer.reviewer': 'Reviewer',
'reviews.drawer.reviewedAt': 'Decided at',
'reviews.drawer.reviewComment': 'Comment',
'reviews.drawer.ownPendingHint':
'This is your own draft under review. Approve/reject are disabled — you can withdraw it.',
'reviews.action.close': 'Close',
'reviews.action.withdraw': 'Withdraw',
'reviews.action.withdrawConfirm':
'Withdraw your draft? It will move to Decided.',
'reviews.schema.title': 'Schema changes',
'reviews.schema.label': 'Schema workflow',
'reviews.schema.queueTotal_one': '{{count}} draft awaiting review',
'reviews.schema.queueTotal_other': '{{count}} drafts awaiting review',
'reviews.schema.dictionary': 'Dictionary',
'reviews.schema.fromVersion': 'From version',
'reviews.schema.maker': 'Author',
'reviews.schema.submittedAt': 'Submitted',
'reviews.schema.action': 'Action',
'reviews.schema.open': 'Open',
// === Lineage (dict-relationships-v2 Phase 1) ===
'lineage.usedBy.title': 'Used by',
'lineage.usedBy.count_one': '{{count}} reference',
'lineage.usedBy.count_other': '{{count}} references',
'lineage.usedBy.activeCount_one': '{{count}} record',
'lineage.usedBy.activeCount_other': '{{count}} records',
'lineage.usedBy.openSourceDict': 'Open dictionary {{dict}}',
'lineage.refs.title': 'References to this record',
'lineage.refs.total_one': '{{count}} reference total',
'lineage.refs.total_other': '{{count}} references total',
'lineage.refs.field': 'field {{field}}',
'lineage.refs.created': 'created {{when}}',
'lineage.refs.openSourceRecord': 'Open referencing record {{key}}',
'lineage.refs.stale': 'refreshed {{minutes}} min ago',
'lineage.refs.staleHint': 'Data from materialized view, refreshed once per minute. Records closed between refreshes are filtered by valid_to.',
// === Time-travel (CEO plan v1 stretch) ===
'timeTravel.button': 'Time-travel…',
'timeTravel.label': 'Active version as of',
'timeTravel.viewing': 'Viewing as of: {{date}}',
'timeTravel.clear': 'Reset',
'timeTravel.close': 'Close',
'timeTravel.date': 'Date',
'timeTravel.time': 'Time',
'timeTravel.hours': 'Hours',
'timeTravel.minutes': 'Minutes',
'timeTravel.quickPresets': 'Quick:',
'timeTravel.preset.now': 'Now',
'timeTravel.preset.hourAgo': '1 hour',
'timeTravel.preset.dayAgo': 'Yesterday',
'timeTravel.preset.weekAgo': '7 days',
'timeTravel.preset.monthAgo': '30 days',
'timeTravel.preset.yearAgo': '1 year',
'timeTravel.hint': 'Click or drag the dot on the axis to pick a moment.',
// === Cascade close (Phase 3) ===
'cascade.title': 'Cascade close',
'cascade.summary.willClose_one': '{{total}} record will be closed (including «{{target}}»). Confirmation required.',
'cascade.summary.willClose_other': '{{total}} records will be closed (including «{{target}}»). Confirmation required.',
'cascade.summary.blocked_one': 'Cannot close «{{target}}»: {{blockers}} active dependency.',
'cascade.summary.blocked_other': 'Cannot close «{{target}}»: {{blockers}} active dependencies.',
'cascade.blockers.title_one': '{{count}} record blocks close',
'cascade.blockers.title_other': '{{count}} records block close',
'cascade.blockers.more': 'and {{count}} more…',
'cascade.blockers.resolveFirst': 'Close or repoint these references first, then retry.',
'cascade.cascade.title_one': '{{count}} dependent record will be cascaded',
'cascade.cascade.title_other': '{{count}} dependent records will be cascaded',
'cascade.cascade.more': 'and {{count}} more…',
'cascade.warnings.title_one': '{{count}} record will become orphan (warn-mode)',
'cascade.warnings.title_other': '{{count}} records will become orphan (warn-mode)',
'cascade.warnings.note': 'After close they will become orphan and visible to orphan scanner. References can be fixed later.',
'cascade.reason.label': 'Reason (optional)',
'cascade.reason.placeholder': 'e.g. decommissioning station SAR-X',
'cascade.confirmGate.label': 'Type {{word}} to confirm',
'cascade.action.cancel': 'Cancel',
'cascade.action.confirm_one': 'Close {{count}} record',
'cascade.action.confirm_other': 'Close {{count}} records',
'lineage.onClose.BLOCK': 'block',
'lineage.onClose.WARN': 'warn',
'lineage.onClose.CASCADE': 'cascade',
'pagination.prev': 'Prev',
'pagination.next': 'Next',
'pagination.pageOf': 'page {{page}} of {{total}}',
'schema.empty': 'No fields yet — add the first one',
'schema.addProperty': 'Add field',
'schema.delete': 'Delete',
'schema.moveUp': 'Move up',
'schema.moveDown': 'Move down',
'schema.kind.label': 'Type',
'schema.kind.string': 'String',
'schema.kind.integer': 'Integer',
'schema.kind.number': 'Number',
'schema.kind.boolean': 'Boolean',
'schema.kind.enum': 'Enum (pick from list)',
'schema.kind.reference': 'Reference to dictionary (FK)',
'schema.kind.localized': 'I18N string (ru-RU/en-US/...)',
'schema.kind.date': 'Date',
'schema.kind.datetime': 'Date+time',
'schema.kind.array_string': 'Array of strings',
'schema.kind.opaque': 'Complex (JSON)',
'schema.opaque.title': 'Complex field — edit only via JSON tab',
'schema.opaque.hint': 'Nested object / array of objects. No editor in Fields tab, raw schema shown below. Edit via JSON tab.',
'schema.reference.title': 'FK to dictionary record',
'schema.reference.hint': 'Value is validated on save: must match the business-key or specified field of an existing record in the target dictionary.',
'schema.reference.targetDict': 'Target dictionary',
'schema.reference.targetField': 'Field',
'schema.reference.targetFieldHint': 'Usually code or business_key',
'schema.reference.required': 'Fill both target dictionary and field — otherwise FK will not work.',
'field.name': 'Name',
'field.designator': 'Designator (COSPAR)',
'field.norad_id': 'NORAD ID',
'field.status': 'Status',
'field.country': 'Country',
'field.operator': 'Operator',
'field.mass_kg': 'Mass (kg)',
'field.mission': 'Mission',
'field.orbit': 'Orbit',
'field.launch_date': 'Launch date',
'field.decay_date': 'Decay date',
'field.spectrum_bands': 'Spectrum bands',
'field.satellite_type_code': 'Satellite type code',
'field.code': 'Code',
'field.description': 'Description',
'field.location': 'Location',
'field.coordinates': 'Coordinates',
'field.frequency_bands': 'Frequency bands',
'loading': 'Loading...',
'error.failed': 'Failed to load data',
'auth.login': 'Sign in',
'auth.logout': 'Sign out',
'dict.filter.searchPlaceholder': 'Search records…',
'dict.filter.scopeLabel': 'Filter by scope',
'dict.filter.matched': 'Matched {{count}} of {{total}}',
'dict.filter.clear': 'Clear',
'dict.filter.noMatches': 'No records match the filter',
'aoi.button': 'AOI filter',
'aoi.activeBbox': 'AOI: bbox {{value}}',
'aoi.activePolygon': 'AOI: polygon ({{points}} points)',
'aoi.clear': 'Clear AOI',
'aoi.dialog.title': 'Select Area of Interest (AOI)',
'aoi.dialog.help': 'Draw a rectangle or polygon. Applied as filter to records with spatial geometry.',
'aoi.dialog.apply': 'Apply',
'aoi.dialog.drawRectangle': 'Rectangle',
'aoi.dialog.drawPolygon': 'Polygon',
'aoi.dialog.clear': 'Clear',
'aoi.dialog.polygonHint': 'Click adds vertex, double-click closes polygon (min 3 vertices)',
'aoi.dialog.mapAriaLabel': 'Map for selecting area of interest',
'aoi.error.noShape': 'Draw an area on the map first',
'aoi.error.invalid': 'Invalid geometry — please redraw',
},
},
},
})
export default i18n