.md files as HTML pages.",
+ "staticFoot": "The webhook secret for git push triggers lives on the workload's Webhook panel after creation.",
+ "sourceConfigJsonTitle": "source_config.json · {kind}",
+ "sourceConfigJsonAria": "Source plugin configuration (JSON)",
+ "triggerNumLabel": "Trigger",
+ "triggerNumOptional": "OPTIONAL",
+ "triggerNewTag": "NEW",
+ "triggerPickTag": "PICK",
+ "triggerSkipTag": "SKIP",
+ "noteSkipTag": "SKIP",
+ "noteEmptyTag": "∅",
+ "faceLabel": "Public face",
+ "faceOptional": "OPTIONAL",
+ "faceSubdomain": "Subdomain",
+ "faceSubdomainPlaceholder": "myapp",
+ "faceDomain": "Domain",
+ "faceDomainPlaceholder": "(inherit from settings)",
+ "facePort": "Target port",
+ "faceHint": "Leave blank to skip provisioning a proxy route. Filling any field creates a single face row attached to this workload.",
+ "cancel": "Cancel",
+ "submit": "Forge app",
+ "submitting": "Forging…",
"triggers": {
"section": "Trigger",
"sectionSub": "Optional. Pick how this app gets a redeploy signal — registry watcher, git event, or manual button.",
@@ -1151,6 +1271,160 @@
}
},
"detail": {
+ "pageTitleFallback": "App",
+ "backLabel": "Back to apps",
+ "eyebrowSuffix": "APP",
+ "kickerId": "id: {id}",
+ "loading": "Loading workload…",
+ "loadError": "Failed to load app",
+ "deployError": "Deploy failed",
+ "saveError": "Save failed",
+ "deleteError": "Delete failed",
+ "alertTag": "ERR",
+ "createdAt": "created",
+ "refreshLabel": "Refresh",
+ "editButton": "Edit",
+ "deleteButton": "Delete",
+ "editTitle": "Edit configuration",
+ "editSubPrefix": "Source",
+ "editSubSuffix": "· triggers managed in the Triggers panel below",
+ "editFieldName": "Name",
+ "editFieldParent": "Parent workload",
+ "editFieldOptional": "OPTIONAL",
+ "editFieldParentPlaceholder": "workload UUID (blank for root)",
+ "editSourceConfig": "Source config",
+ "editConfigYaml": "YAML",
+ "editConfigForm": "FORM",
+ "editConfigJson": "JSON",
+ "advancedJson": "Advanced JSON",
+ "backToForm": "Back to form",
+ "switchToJsonTitle": "Switch to the raw JSON editor",
+ "switchToFormTitle": "Switch back to the form",
+ "jsonOk": "JSON OK",
+ "jsonInvalid": "JSON INVALID",
+ "editComposeProject": "Compose project name (optional)",
+ "editComposeProjectPlaceholder": "(defaults to sanitized workload name)",
+ "editComposePlaceholder": "services:\n web:\n image: nginx:alpine",
+ "editComposeAria": "Compose YAML",
+ "editComposeHeader": "compose.yaml",
+ "editImageHeader": "image source · runtime knobs",
+ "editImageRef": "Image (registry path)",
+ "editImageRefPlaceholder": "registry.example.com/owner/app",
+ "editImagePort": "Port",
+ "editImageHealthcheck": "Healthcheck path",
+ "editImageDefaultTag": "Default tag",
+ "editImageRegistry": "Registry (for private pulls)",
+ "editImageRegistryPublic": "(public — no auth)",
+ "editImageCpu": "CPU limit (cores, 0 = ∞)",
+ "editImageMemory": "Memory limit (MB, 0 = ∞)",
+ "editImageMax": "Max instances",
+ "editImageFoot": "Env vars and volume mounts use their own panels below — saving here preserves them.",
+ "editStaticHeader": "static source · pages from a repo",
+ "editStaticProvider": "Provider",
+ "editStaticBaseUrl": "Base URL",
+ "editStaticBaseUrlPlaceholder": "https://git.example.com",
+ "editStaticRepoOwner": "Repo owner",
+ "editStaticRepoName": "Repo name",
+ "editStaticBranch": "Branch",
+ "editStaticFolder": "Folder path (optional)",
+ "editStaticFolderPlaceholder": "(repo root)",
+ "editStaticToken": "Access token (private repos)",
+ "editStaticTokenPlaceholder": "(leave blank for public repos)",
+ "editStaticMode": "Mode",
+ "editStaticModeStaticDesc": "— serve files via nginx.",
+ "editStaticModeDenoDesc": "— Deno runtime with dynamic routing.",
+ "editStaticRenderMarkdown": "Render markdown",
+ "editStaticRenderMarkdownDesc": "— auto-render .md as HTML.",
+ "editSourceJsonHeader": "source_config.json",
+ "editSourceJsonAria": "Source plugin configuration (JSON)",
+ "editPublicFaces": "Public faces",
+ "editPublicFacesTag": "JSON ARRAY",
+ "editPublicFacesHeader": "public_faces.json",
+ "editPublicFacesAria": "Public faces configuration (JSON array)",
+ "editCancel": "Cancel",
+ "editSave": "Save changes",
+ "editSaving": "Saving…",
+ "manualDeployTitle": "Manual deploy",
+ "manualDeployOk": "OK",
+ "manualDeployDispatched": "Dispatched {reference} as {by}",
+ "manualDeployRefAria": "Deploy reference",
+ "manualDeployRefPlaceholder": "reference (image tag, git sha, blank for default)",
+ "manualDeployButton": "Deploy",
+ "manualDeployDispatching": "Dispatching…",
+ "manualDeployHint": "Use a specific image tag, git sha, or branch to force a deploy. Leave blank to use the default reference resolved by the source plugin.",
+ "containersTitle": "Containers",
+ "containersEmpty": "No containers yet",
+ "containersCount": "{count} reconciled",
+ "containersEmptyInline": "No containers yet — deploy to spin one up.",
+ "containersColRole": "Role",
+ "containersColState": "State",
+ "containersColImage": "Image",
+ "containersColSubdomain": "Subdomain",
+ "containersColLastSeen": "Last seen",
+ "containersColActions": "Actions",
+ "containersLogsAction": "Logs",
+ "chainTitle": "Chain",
+ "chainSubFromParent": "promotes from a parent",
+ "chainSubParentOf": "parent of",
+ "chainChildSingular": "child",
+ "chainChildPlural": "children",
+ "chainParentLabel": "Parent",
+ "chainSelfLabel": "This",
+ "chainChildrenLabel": "Children",
+ "chainPromoteButton": "Promote from parent",
+ "chainPromoting": "Promoting…",
+ "chainHint": "Set parent_workload_id on a workload to build a chain. Image-source children can promote the parent's currently-running tag with one click.",
+ "volumesTitle": "Volumes",
+ "volumesEmpty": "No mounts",
+ "volumesCountSingular": "{count} mount",
+ "volumesCountPlural": "{count} mounts",
+ "volumesColTarget": "Target",
+ "volumesColSource": "Source",
+ "volumesColScope": "Scope",
+ "volumesColUpdated": "Updated",
+ "volumesColActions": "Actions",
+ "volumeSource": "Source (host)",
+ "volumeSourcePlaceholder": "/srv/data/myapp",
+ "volumeTarget": "Target (container)",
+ "volumeTargetPlaceholder": "/data",
+ "volumeScope": "Scope",
+ "volumeAddButton": "Add / Replace",
+ "volumeSaving": "Saving…",
+ "volumeHint": "Absolute mounts bind a host path into the container. Non-absolute scopes are accepted for future use; only absolute is honoured at deploy time today.",
+ "volumeTargetError": "Target must be an absolute container path (e.g. /data)",
+ "volumeSetFailed": "Failed to set volume",
+ "volumeDeleteFailed": "Failed to delete volume",
+ "envTitle": "Env",
+ "envEmpty": "No overrides",
+ "envCountSingular": "{count} override",
+ "envCountPlural": "{count} overrides",
+ "envColKey": "Key",
+ "envColValue": "Value",
+ "envColUpdated": "Updated",
+ "envColActions": "Actions",
+ "envEncrypted": "ENCRYPTED",
+ "envKey": "Key",
+ "envKeyPlaceholder": "DATABASE_URL",
+ "envValue": "Value",
+ "envValuePlaceholder": "(empty to unset)",
+ "envEncryptLabel": "Encrypt at rest",
+ "envAddButton": "Add / Replace",
+ "envSaving": "Saving…",
+ "envHint": "Encrypted values are write-only after store — the API redacts them on read. Rotate by setting a new value.",
+ "envKeyRequired": "Key is required",
+ "envSetFailed": "Failed to set env",
+ "envDeleteFailed": "Failed to delete env",
+ "sourceConfigTitle": "Source config",
+ "sourceConfigCopy": "Copy",
+ "sourceConfigCopied": "Copied",
+ "sourceConfigCopyAria": "Copy source config",
+ "publicFacesTitle": "Public faces",
+ "publicFacesCopyAria": "Copy public faces",
+ "deleteConfirmTitle": "Delete this app?",
+ "deleteConfirmMessage": "Tears down all containers and proxy routes owned by \"{name}\", then removes the row. This cannot be undone.",
+ "deleteConfirmFallbackName": "this workload",
+ "deleteConfirmYes": "Yes, delete",
+ "deleteConfirmDeleting": "Deleting…",
"manualDeploySub": "Bypasses configured triggers and dispatches through the source plugin directly.",
"chainTriggersZero": "no triggers",
"chainTriggersOne": "1 trigger",
diff --git a/web/src/lib/i18n/ru.json b/web/src/lib/i18n/ru.json
index 5f3beb5..0c609a1 100644
--- a/web/src/lib/i18n/ru.json
+++ b/web/src/lib/i18n/ru.json
@@ -1128,7 +1128,127 @@
}
},
"apps": {
+ "list": {
+ "eyebrowSuffix": "ПРИЛОЖЕНИЯ",
+ "title": "Приложения",
+ "ledePrefix": "Plugin-native деплои —",
+ "ledeKindImage": "image",
+ "ledeKindCompose": "compose",
+ "ledeKindStatic": "static",
+ "ledeMiddle": ", или",
+ "ledeSuffix": ", с подключаемыми триггерами передеплоя. Старые проекты, стеки и сайты на время переезда остаются в собственных разделах.",
+ "statTotal": "ВСЕГО",
+ "statImage": "IMAGE",
+ "statCompose": "COMPOSE",
+ "statStatic": "STATIC",
+ "refresh": "Обновить",
+ "newApp": "Новое приложение",
+ "filterAriaLabel": "Фильтр по source-плагину",
+ "filterAll": "ВСЕ",
+ "loadError": "Не удалось загрузить приложения",
+ "alertTag": "ОШ",
+ "emptyTitle": "Приложений пока нет",
+ "emptyBody": "Приложения объединяют image, compose и static-деплои за единым plugin-driven интерфейсом. Создайте первое, чтобы увидеть его здесь.",
+ "emptyCta": "Создать первое приложение",
+ "colName": "Имя",
+ "colSource": "Источник",
+ "colTrigger": "Триггер",
+ "colCreated": "Создано",
+ "colActions": "Действия",
+ "rowOpen": "Открыть"
+ },
"new": {
+ "pageTitle": "Новое приложение · Tinyforge",
+ "backLabel": "К приложениям",
+ "eyebrowSuffix": "НОВОЕ ПРИЛОЖЕНИЕ",
+ "title": "Создать приложение",
+ "ledePrefix": "Создайте plugin-native нагрузку.",
+ "ledeSourceLabel": "Источник",
+ "ledeSourceMid": "= как она деплоится (image, compose, static). Выберите или создайте",
+ "ledeTriggerLabel": "триггер",
+ "ledeSuffix": "ниже — при срабатывании source-плагин передеплоит приложение.",
+ "loadingKinds": "Загрузка доступных видов плагинов…",
+ "alertTag": "ОШ",
+ "fieldName": "Имя",
+ "fieldNameRequired": "ОБЯЗАТЕЛЬНО",
+ "fieldNamePlaceholder": "my-app",
+ "fieldNameHint": "В нижнем регистре, без пробелов. Используется в именах контейнеров и поддоменах.",
+ "fieldSourcePlugin": "Source-плагин",
+ "fieldSourceLabel": "Источник",
+ "fieldSourceHint": "Список берётся из запущенного демона — видны только вкомпилированные плагины. Триггеры (registry / git / manual) настраиваются ниже как отдельные записи.",
+ "fieldSourceConfig": "Конфигурация источника",
+ "fieldConfigYaml": "YAML",
+ "fieldConfigForm": "ФОРМА",
+ "fieldConfigJson": "JSON",
+ "advancedJson": "Расширенный JSON",
+ "backToForm": "К форме",
+ "resetSample": "Сбросить к примеру",
+ "switchToJsonTitle": "Переключиться на сырой JSON-редактор",
+ "switchToFormTitle": "Вернуться к форме",
+ "jsonOk": "JSON ОК",
+ "jsonInvalid": "JSON НЕВЕРНЫЙ",
+ "linesUnit": "строк",
+ "composeHeader": "compose.yaml · compose",
+ "composeAriaLabel": "Compose YAML",
+ "composeProjectLabel": "Имя compose-проекта (опционально)",
+ "composeProjectPlaceholder": "(по умолчанию — нормализованное имя нагрузки)",
+ "composePlaceholder": "services:\n web:\n image: nginx:alpine\n ports:\n - \"80\"",
+ "imageHeader": "image-источник · параметры рантайма",
+ "imageRefLabel": "Образ (путь в реестре)",
+ "imageRefPlaceholder": "registry.example.com/owner/app",
+ "imageRefHint": "Полная ссылка без тега; тег задаётся при деплое — триггером или полем «Тег по умолчанию» ниже.",
+ "imagePort": "Порт",
+ "imageHealthcheck": "Путь healthcheck",
+ "imageDefaultTag": "Тег по умолчанию",
+ "imageRegistryLabel": "Реестр (для приватных pull-ов)",
+ "imageRegistryPublic": "(публичный — без авторизации)",
+ "imageRegistryHint": "Имя должно совпадать с записью на странице «Реестры». Оставьте пустым для публичных образов.",
+ "imageCpu": "Лимит CPU (ядра, 0 = ∞)",
+ "imageMemory": "Лимит памяти (МБ, 0 = ∞)",
+ "imageMax": "Макс. инстансов",
+ "imageMaxHint": "1 = строгий blue-green.",
+ "imageFoot": "Переменные окружения и тома задаются в отдельных панелях на странице нагрузки после создания.",
+ "staticHeader": "static-источник · страницы из репозитория",
+ "staticProvider": "Провайдер",
+ "staticBaseUrl": "Base URL",
+ "staticBaseUrlPlaceholder": "https://git.example.com",
+ "staticRepoOwner": "Владелец репозитория",
+ "staticRepoOwnerPlaceholder": "owner",
+ "staticRepoName": "Имя репозитория",
+ "staticRepoNamePlaceholder": "pages",
+ "staticBranch": "Ветка",
+ "staticBranchPlaceholder": "main",
+ "staticFolder": "Папка (опционально)",
+ "staticFolderPlaceholder": "(корень репозитория)",
+ "staticToken": "Токен доступа (приватные репозитории)",
+ "staticTokenPlaceholder": "(оставьте пустым для публичных репозиториев)",
+ "staticTokenHint": "Шифруется при хранении. Нужен только для приватных репозиториев.",
+ "staticMode": "Режим",
+ "staticModeStaticDesc": "— раздача файлов через nginx; ноль рантайм-оверхеда.",
+ "staticModeDenoDesc": "— Deno-рантайм с опциональной динамической маршрутизацией.",
+ "staticRenderMarkdown": "Рендерить markdown",
+ "staticRenderMarkdownDesc": "— автоматически отдавать .md файлы как HTML-страницы.",
+ "staticFoot": "Секрет вебхука для git push-триггеров появляется в панели вебхука нагрузки после создания.",
+ "sourceConfigJsonTitle": "source_config.json · {kind}",
+ "sourceConfigJsonAria": "Конфигурация source-плагина (JSON)",
+ "triggerNumLabel": "Триггер",
+ "triggerNumOptional": "ОПЦИОНАЛЬНО",
+ "triggerNewTag": "НОВЫЙ",
+ "triggerPickTag": "ВЫБРАТЬ",
+ "triggerSkipTag": "ПРОПУСК",
+ "noteSkipTag": "ПРОПУСК",
+ "noteEmptyTag": "∅",
+ "faceLabel": "Публичный фронт",
+ "faceOptional": "ОПЦИОНАЛЬНО",
+ "faceSubdomain": "Поддомен",
+ "faceSubdomainPlaceholder": "myapp",
+ "faceDomain": "Домен",
+ "faceDomainPlaceholder": "(наследуется из настроек)",
+ "facePort": "Целевой порт",
+ "faceHint": "Оставьте пустым, чтобы не создавать прокси-маршрут. Заполнение любого поля создаст одну запись фронта, привязанную к этой нагрузке.",
+ "cancel": "Отмена",
+ "submit": "Создать приложение",
+ "submitting": "Создание…",
"triggers": {
"section": "Триггер",
"sectionSub": "Необязательно. Выберите, откуда придёт сигнал передеплоя — слежение за реестром, git-событие или ручная кнопка.",
@@ -1151,6 +1271,160 @@
}
},
"detail": {
+ "pageTitleFallback": "Приложение",
+ "backLabel": "К приложениям",
+ "eyebrowSuffix": "ПРИЛОЖЕНИЕ",
+ "kickerId": "id: {id}",
+ "loading": "Загрузка нагрузки…",
+ "loadError": "Не удалось загрузить приложение",
+ "deployError": "Деплой не удался",
+ "saveError": "Сохранение не удалось",
+ "deleteError": "Удаление не удалось",
+ "alertTag": "ОШ",
+ "createdAt": "создано",
+ "refreshLabel": "Обновить",
+ "editButton": "Изменить",
+ "deleteButton": "Удалить",
+ "editTitle": "Редактирование конфигурации",
+ "editSubPrefix": "Источник",
+ "editSubSuffix": "· триггеры настраиваются в панели «Триггеры» ниже",
+ "editFieldName": "Имя",
+ "editFieldParent": "Родительская нагрузка",
+ "editFieldOptional": "ОПЦИОНАЛЬНО",
+ "editFieldParentPlaceholder": "UUID нагрузки (пусто для корневой)",
+ "editSourceConfig": "Конфигурация источника",
+ "editConfigYaml": "YAML",
+ "editConfigForm": "ФОРМА",
+ "editConfigJson": "JSON",
+ "advancedJson": "Расширенный JSON",
+ "backToForm": "К форме",
+ "switchToJsonTitle": "Переключиться на сырой JSON-редактор",
+ "switchToFormTitle": "Вернуться к форме",
+ "jsonOk": "JSON ОК",
+ "jsonInvalid": "JSON НЕВЕРНЫЙ",
+ "editComposeProject": "Имя compose-проекта (опционально)",
+ "editComposeProjectPlaceholder": "(по умолчанию — нормализованное имя нагрузки)",
+ "editComposePlaceholder": "services:\n web:\n image: nginx:alpine",
+ "editComposeAria": "Compose YAML",
+ "editComposeHeader": "compose.yaml",
+ "editImageHeader": "image-источник · параметры рантайма",
+ "editImageRef": "Образ (путь в реестре)",
+ "editImageRefPlaceholder": "registry.example.com/owner/app",
+ "editImagePort": "Порт",
+ "editImageHealthcheck": "Путь healthcheck",
+ "editImageDefaultTag": "Тег по умолчанию",
+ "editImageRegistry": "Реестр (для приватных pull-ов)",
+ "editImageRegistryPublic": "(публичный — без авторизации)",
+ "editImageCpu": "Лимит CPU (ядра, 0 = ∞)",
+ "editImageMemory": "Лимит памяти (МБ, 0 = ∞)",
+ "editImageMax": "Макс. инстансов",
+ "editImageFoot": "Переменные окружения и тома живут в своих панелях ниже — сохранение здесь их не затронет.",
+ "editStaticHeader": "static-источник · страницы из репозитория",
+ "editStaticProvider": "Провайдер",
+ "editStaticBaseUrl": "Base URL",
+ "editStaticBaseUrlPlaceholder": "https://git.example.com",
+ "editStaticRepoOwner": "Владелец репозитория",
+ "editStaticRepoName": "Имя репозитория",
+ "editStaticBranch": "Ветка",
+ "editStaticFolder": "Папка (опционально)",
+ "editStaticFolderPlaceholder": "(корень репозитория)",
+ "editStaticToken": "Токен доступа (приватные репозитории)",
+ "editStaticTokenPlaceholder": "(оставьте пустым для публичных репозиториев)",
+ "editStaticMode": "Режим",
+ "editStaticModeStaticDesc": "— раздача файлов через nginx.",
+ "editStaticModeDenoDesc": "— Deno-рантайм с динамической маршрутизацией.",
+ "editStaticRenderMarkdown": "Рендерить markdown",
+ "editStaticRenderMarkdownDesc": "— автоматически отдавать .md как HTML.",
+ "editSourceJsonHeader": "source_config.json",
+ "editSourceJsonAria": "Конфигурация source-плагина (JSON)",
+ "editPublicFaces": "Публичные фронты",
+ "editPublicFacesTag": "JSON МАССИВ",
+ "editPublicFacesHeader": "public_faces.json",
+ "editPublicFacesAria": "Конфигурация публичных фронтов (JSON-массив)",
+ "editCancel": "Отмена",
+ "editSave": "Сохранить",
+ "editSaving": "Сохранение…",
+ "manualDeployTitle": "Ручной деплой",
+ "manualDeployOk": "ОК",
+ "manualDeployDispatched": "Отправлено {reference} как {by}",
+ "manualDeployRefAria": "Референс для деплоя",
+ "manualDeployRefPlaceholder": "референс (тег образа, git sha; пусто — по умолчанию)",
+ "manualDeployButton": "Деплой",
+ "manualDeployDispatching": "Отправка…",
+ "manualDeployHint": "Задайте конкретный тег образа, git sha или ветку, чтобы принудительно деплоить. Оставьте пустым, чтобы source-плагин выбрал референс по умолчанию.",
+ "containersTitle": "Контейнеры",
+ "containersEmpty": "Контейнеров пока нет",
+ "containersCount": "{count} согласовано",
+ "containersEmptyInline": "Контейнеров пока нет — задеплойте, чтобы поднять первый.",
+ "containersColRole": "Роль",
+ "containersColState": "Состояние",
+ "containersColImage": "Образ",
+ "containersColSubdomain": "Поддомен",
+ "containersColLastSeen": "Последний раз виден",
+ "containersColActions": "Действия",
+ "containersLogsAction": "Логи",
+ "chainTitle": "Цепочка",
+ "chainSubFromParent": "продвигается от родителя",
+ "chainSubParentOf": "родитель для",
+ "chainChildSingular": "дочерней нагрузки",
+ "chainChildPlural": "дочерних нагрузок",
+ "chainParentLabel": "Родитель",
+ "chainSelfLabel": "Эта",
+ "chainChildrenLabel": "Дочерние",
+ "chainPromoteButton": "Продвинуть от родителя",
+ "chainPromoting": "Продвижение…",
+ "chainHint": "Задайте parent_workload_id у нагрузки, чтобы построить цепочку. Дочерние image-источники могут одним кликом продвинуть текущий запущенный тег родителя.",
+ "volumesTitle": "Тома",
+ "volumesEmpty": "Нет монтирований",
+ "volumesCountSingular": "{count} монтирование",
+ "volumesCountPlural": "{count} монтирований",
+ "volumesColTarget": "Цель",
+ "volumesColSource": "Источник",
+ "volumesColScope": "Скоуп",
+ "volumesColUpdated": "Обновлено",
+ "volumesColActions": "Действия",
+ "volumeSource": "Источник (хост)",
+ "volumeSourcePlaceholder": "/srv/data/myapp",
+ "volumeTarget": "Цель (контейнер)",
+ "volumeTargetPlaceholder": "/data",
+ "volumeScope": "Скоуп",
+ "volumeAddButton": "Добавить / Заменить",
+ "volumeSaving": "Сохранение…",
+ "volumeHint": "Абсолютные монтирования прокидывают путь хоста в контейнер. Другие скоупы приняты для будущих сценариев; сегодня при деплое применяется только absolute.",
+ "volumeTargetError": "Цель должна быть абсолютным путём в контейнере (например, /data)",
+ "volumeSetFailed": "Не удалось задать том",
+ "volumeDeleteFailed": "Не удалось удалить том",
+ "envTitle": "Окружение",
+ "envEmpty": "Нет переопределений",
+ "envCountSingular": "{count} переопределение",
+ "envCountPlural": "{count} переопределений",
+ "envColKey": "Ключ",
+ "envColValue": "Значение",
+ "envColUpdated": "Обновлено",
+ "envColActions": "Действия",
+ "envEncrypted": "ЗАШИФРОВАНО",
+ "envKey": "Ключ",
+ "envKeyPlaceholder": "DATABASE_URL",
+ "envValue": "Значение",
+ "envValuePlaceholder": "(пусто — снять)",
+ "envEncryptLabel": "Шифровать при хранении",
+ "envAddButton": "Добавить / Заменить",
+ "envSaving": "Сохранение…",
+ "envHint": "Зашифрованные значения после записи доступны только на запись — API скрывает их при чтении. Ротация — установка нового значения.",
+ "envKeyRequired": "Ключ обязателен",
+ "envSetFailed": "Не удалось задать переменную",
+ "envDeleteFailed": "Не удалось удалить переменную",
+ "sourceConfigTitle": "Конфигурация источника",
+ "sourceConfigCopy": "Копировать",
+ "sourceConfigCopied": "Скопировано",
+ "sourceConfigCopyAria": "Копировать конфиг источника",
+ "publicFacesTitle": "Публичные фронты",
+ "publicFacesCopyAria": "Копировать публичные фронты",
+ "deleteConfirmTitle": "Удалить это приложение?",
+ "deleteConfirmMessage": "Сносит все контейнеры и прокси-маршруты, принадлежащие «{name}», затем удаляет запись. Это нельзя отменить.",
+ "deleteConfirmFallbackName": "эта нагрузка",
+ "deleteConfirmYes": "Да, удалить",
+ "deleteConfirmDeleting": "Удаление…",
"manualDeploySub": "Обходит настроенные триггеры и отправляет деплой напрямую через source-плагин.",
"chainTriggersZero": "без триггеров",
"chainTriggersOne": "1 триггер",
diff --git a/web/src/routes/apps/+page.svelte b/web/src/routes/apps/+page.svelte
index f79d832..5dd4be2 100644
--- a/web/src/routes/apps/+page.svelte
+++ b/web/src/routes/apps/+page.svelte
@@ -4,6 +4,7 @@
import * as api from '$lib/api';
import { IconPlus, IconRefresh } from '$lib/components/icons';
import ForgeHero from '$lib/components/ForgeHero.svelte';
+ import { t } from '$lib/i18n';
let workloads = $state