Files
mdm-ordinis/ordinis-admin-ui/src/i18n.ts
T
2026-05-12 11:33:01 +00:00

1273 lines
86 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': 'Администрирование',
'topbar.search.label': 'Поиск',
'topbar.search.placeholder': 'Поиск по справочникам, ⌘K',
'graph.title': 'Граф связей справочников',
'graph.description': 'Сетевая диаграмма всех справочников и их FK-связей. Кликните по узлу — откроется детальный вид.',
'graph.comingSoon.title': 'Скоро',
'graph.comingSoon.description': 'Visualization через d3-force — 40+ справочников с linked edges. В разработке (Stage 3.3 redesign roadmap).',
'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': 'Все ваши submissions с историей: pending, approved, rejected, withdrawn.',
'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': 'Reviewer',
'myDrafts.col.comment': 'Комментарий',
'myDrafts.col.actions': 'Действия',
'myDrafts.action.withdraw': 'Отозвать',
'myDrafts.confirmWithdraw': 'Отозвать draft? Это безвозвратно — статус станет WITHDRAWN.',
'reviews.title': 'Очередь согласований',
'reviews.description': 'Pending drafts от makers, ждут approve/reject. Pool model: первый approver обрабатывает.',
'reviews.empty': 'Нет pending согласований. Все обработаны 👍',
'reviews.queueTotal_one': '{{count}} pending draft',
'reviews.queueTotal_few': '{{count}} pending draft',
'reviews.queueTotal_many': '{{count}} pending drafts',
'reviews.queueTotal_other': '{{count}} pending drafts',
'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.approvalMinRole.label': 'Минимальная роль ревьюера (опционально)',
'schema.approvalMinRole.hint': 'Например ordinis:internal или ordinis:restricted. Пусто = любой пользователь с ролью ordinis:approver. (Phase 2 — enforcement TBD).',
'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': 'Типы событий (CSV)',
'webhooks.field.eventTypeFilterHint': 'record.created, record.updated, record.closed. Пусто = все',
'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.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.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': 'Пользователь',
'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.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': 'Записи',
// List view (handoff D)
'dict.list.bundle.all': 'все',
'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.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}}',
'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 и locale выбранного словаря. Имя оставьте уникальное; версия сбросится на 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': '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.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',
'topbar.search.label': 'Search',
'topbar.search.placeholder': 'Search dictionaries, ⌘K',
'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).',
'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.approvalMinRole.label': 'Minimum reviewer role (optional)',
'schema.approvalMinRole.hint': 'e.g. ordinis:internal or ordinis:restricted. Empty = any user with ordinis:approver. (Phase 2 — enforcement TBD).',
'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 (CSV)',
'webhooks.field.eventTypeFilterHint': 'record.created, record.updated, record.closed. Empty = all',
'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.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.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': 'Create',
'audit.action.UPDATE': 'Update',
'audit.action.CLOSE': 'Close',
'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',
'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',
// 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.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': 'Delete',
'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}}',
'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.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