diff --git a/memory-bank/backendApi.md b/memory-bank/backendApi.md index 6a461f8..39fc649 100644 --- a/memory-bank/backendApi.md +++ b/memory-bank/backendApi.md @@ -39,6 +39,7 @@ Every command is `async`. Every command returns `Result` (serializ |---|---|---|---|---|---| | `brew_search` | `async fn brew_search(query: String) -> Result` | Searches the Homebrew index for matching formulae and casks. | `brew search --formula ` and `brew search --cask ` (parallel within the command; plain stdout — no `--json` support) | no | shared-read | | `brew_search_desc` | `async fn brew_search_desc(query: String) -> Result` | Searches by description text. Optional / "Phase 2.1" if time permits. | `brew search --desc ` | no | shared-read | +| `local_search` | `async fn local_search(query: String, locale: Option, state) -> Result` | In-memory search across package token/name, upstream desc, bundled enrichment, category labels, tags, and locale overlay search terms. `locale` selects a partial `enrichment..json.gz` overlay when present, while English fields remain searchable fallbacks. | none — scans bundled/user-refreshed catalog already in memory | no | no | ### Phase 3 — Install / uninstall / upgrade (streaming) @@ -1194,8 +1195,8 @@ All six commands share the `authed_gate` helper in `commands/github.rs:152` whic | Command | Signature | Purpose | Paranoid gate | Auth | Source | |---|---|---|---|---|---| -| `enrichment_data` | `async fn enrichment_data(state) -> Result, BrewError>` | Returns the full bundled enrichment payload. Memoised on `AppState.enrichment_cache` — subsequent calls return an Arc clone, not a re-parse. Zero runtime LLM calls; the bundle is `include_bytes!`d at compile time. | no (bundled) | none | `commands/enrichment.rs:23` | -| `enrichment_lookup` | `async fn enrichment_lookup(name: String, state) -> Result, BrewError>` | Per-token lookup. `validate_package_name` gates the input. Returns `None` when the token is missing (placeholder bundle, unmapped package). | no | none | `commands/enrichment.rs:43` | +| `enrichment_data` | `async fn enrichment_data(locale: Option, state) -> Result, BrewError>` | Returns the full bundled enrichment payload, optionally merged with a small locale overlay such as `enrichment.ru.json.gz`. Base English data is memoised on `AppState.enrichment_cache`; localized copies are memoised by locale in `enrichment_locale_cache`. Zero runtime LLM calls; bundles are `include_bytes!`d at compile time. | no (bundled) | none | `commands/enrichment.rs:23` | +| `enrichment_lookup` | `async fn enrichment_lookup(name: String, locale: Option, state) -> Result, BrewError>` | Per-token lookup with the same locale overlay semantics as `enrichment_data`. `validate_package_name` gates the input. Returns `None` when the token is missing (placeholder bundle, unmapped package). | no | none | `commands/enrichment.rs:65` | ### 13.10 New error variants @@ -1364,4 +1365,3 @@ Critical invariants pinned: - Cache corrupt → empty cache returned, no panic - GHSA enrichment soft-fails when `github_enabled` is off - Install-set fingerprint is deterministic across runs (sorted input → stable hash) - diff --git a/native/Package.swift b/native/Package.swift index da2f7a3..e0c7e6d 100644 --- a/native/Package.swift +++ b/native/Package.swift @@ -14,6 +14,7 @@ import PackageDescription // `BrewBrowser` executable is wrapped into a launchable .app by build-app.sh. let package = Package( name: "BrewBrowser", + defaultLocalization: "en", platforms: [.macOS(.v26)], products: [ // Exposing the library as a product makes SwiftPM/Xcode generate a @@ -34,7 +35,12 @@ let package = Package( .executableTarget( name: "BrewBrowser", dependencies: ["BrewBrowserKit"], - path: "Sources/BrewBrowser" + path: "Sources/BrewBrowser", + resources: [ + .process("Resources/Localizable.xcstrings"), + .process("Resources/en.lproj"), + .process("Resources/ru.lproj") + ] ), // Library: all views, AppModel, and the brew/vulns/github/trending/ // enrichment services. Bundled JSON resources live here alongside the @@ -48,8 +54,12 @@ let package = Package( ], path: "Sources/BrewBrowserKit", resources: [ + .process("Resources/Localizable.xcstrings"), + .process("Resources/en.lproj"), + .process("Resources/ru.lproj"), .copy("Resources/categories.json"), .copy("Resources/enrichment.json"), + .copy("Resources/enrichment.ru.json"), // GitHub Octocat mark (vector PDF, Primer/Octicons MIT). Rendered // as a template image in the toolbar's "connected" chip. .copy("Resources/github-mark.pdf"), diff --git a/native/Sources/BrewBrowser/BrewBrowserApp.swift b/native/Sources/BrewBrowser/BrewBrowserApp.swift index 2214851..e9eccf0 100644 --- a/native/Sources/BrewBrowser/BrewBrowserApp.swift +++ b/native/Sources/BrewBrowser/BrewBrowserApp.swift @@ -65,8 +65,8 @@ struct AppCommands: Commands { // mirrors the Tauri AboutModal. A "Sponsor brew-browser…" item sits below // so the donate CTA also lives in the app menu. CommandGroup(replacing: .appInfo) { - Button("About Brew Browser") { model.aboutOpen = true } - Button("Sponsor Brew Browser…") { + Button(L10n.string("about.title")) { model.aboutOpen = true } + Button(L10n.string("chrome.donate.menu")) { if let url = URL(string: AboutInfo.sponsorURL) { NSWorkspace.shared.open(url) } @@ -79,50 +79,50 @@ struct AppCommands: Commands { // Sparkle's documented SwiftUI integration. Mirrors the Tauri "Check now" // (`SettingsSectionUpdates.svelte`). CommandGroup(after: .appInfo) { - Button("Check for Updates…") { updater.checkForUpdates() } + Button(L10n.string("updates.checkForUpdates")) { updater.checkForUpdates() } .disabled(!updater.canCheckForUpdates) } // A dedicated "Go" menu hosts the section-nav shortcuts (⌘0–6) so they // appear together with their key equivalents, like a stock View menu. - CommandMenu("Go") { - Button("Dashboard") { model.go(toSectionNumber: 0) } + CommandMenu(L10n.string("menu.go")) { + Button(L10n.string("nav.dashboard")) { model.go(toSectionNumber: 0) } .keyboardShortcut("0", modifiers: .command) - Button("Library") { model.go(toSectionNumber: 1) } + Button(L10n.string("nav.library")) { model.go(toSectionNumber: 1) } .keyboardShortcut("1", modifiers: .command) - Button("Discover") { model.go(toSectionNumber: 2) } + Button(L10n.string("nav.discover")) { model.go(toSectionNumber: 2) } .keyboardShortcut("2", modifiers: .command) - Button("Trending") { model.go(toSectionNumber: 3) } + Button(L10n.string("nav.trending")) { model.go(toSectionNumber: 3) } .keyboardShortcut("3", modifiers: .command) - Button("Snapshots") { model.go(toSectionNumber: 4) } + Button(L10n.string("nav.snapshots")) { model.go(toSectionNumber: 4) } .keyboardShortcut("4", modifiers: .command) - Button("Services") { model.go(toSectionNumber: 5) } + Button(L10n.string("nav.services")) { model.go(toSectionNumber: 5) } .keyboardShortcut("5", modifiers: .command) - Button("Activity") { model.go(toSectionNumber: 6) } + Button(L10n.string("nav.activity")) { model.go(toSectionNumber: 6) } .keyboardShortcut("6", modifiers: .command) Divider() // ⌘K command palette. - Button("Command Palette…") { model.paletteOpen = true } + Button(L10n.string("palette.title.menu")) { model.paletteOpen = true } .keyboardShortcut("k", modifiers: .command) } // ⌘R refresh + ⌘L drawer toggle replace the stock toolbar/sidebar // command group so they slot into the View menu. CommandGroup(after: .toolbar) { - Button("Refresh") { + Button(L10n.string("action.refresh")) { Task { await model.refreshCurrent() } } .keyboardShortcut("r", modifiers: .command) .disabled(model.isLoading) - Button(model.drawerOpen ? "Hide Activity Drawer" : "Show Activity Drawer") { + Button(model.drawerOpen ? L10n.string("activity.drawer.hide") : L10n.string("activity.drawer.show")) { model.toggleDrawer() } .keyboardShortcut("l", modifiers: .command) - Button("Cycle Theme") { model.cycleTheme() } + Button(L10n.string("theme.cycle")) { model.cycleTheme() } .keyboardShortcut("l", modifiers: [.command, .shift]) } } diff --git a/native/Sources/BrewBrowser/Resources/Localizable.xcstrings b/native/Sources/BrewBrowser/Resources/Localizable.xcstrings new file mode 100644 index 0000000..21ea4ba --- /dev/null +++ b/native/Sources/BrewBrowser/Resources/Localizable.xcstrings @@ -0,0 +1,13286 @@ +{ + "sourceLanguage": "en", + "strings": { + "AI & ML": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "AI & ML" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "ИИ и ML" + } + } + } + }, + "Browsers": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browsers" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Браузеры" + } + } + } + }, + "Cloud & DevOps": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cloud & DevOps" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Облако и DevOps" + } + } + } + }, + "Communication": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Communication" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Коммуникации" + } + } + } + }, + "Data": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Data" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Данные" + } + } + } + }, + "Developer Tools": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Developer Tools" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Инструменты разработчика" + } + } + } + }, + "Editors & IDEs": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Editors & IDEs" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Редакторы и IDE" + } + } + } + }, + "Education": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Education" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Образование" + } + } + } + }, + "Games & Entertainment": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Games & Entertainment" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Игры и развлечения" + } + } + } + }, + "Graphics & Design": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Graphics & Design" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Графика и дизайн" + } + } + } + }, + "Music": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Music" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Музыка" + } + } + } + }, + "Office & Docs": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Office & Docs" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офис и документы" + } + } + } + }, + "Productivity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Productivity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Продуктивность" + } + } + } + }, + "System Utilities": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System Utilities" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Системные утилиты" + } + } + } + }, + "Video & Audio": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Video & Audio" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Видео и аудио" + } + } + } + }, + "Writing": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Writing" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Работа с текстом" + } + } + } + }, + "Uncategorized": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Uncategorized" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Без категории" + } + } + } + }, + "app.controls": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "App controls" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Элементы управления приложением" + } + } + } + }, + "chrome.github.connectionStatus": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub connection status" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Статус подключения GitHub" + } + } + } + }, + "chrome.donate.aria": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Donate to brew-browser on GitHub Sponsors" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поддержать brew-browser через GitHub Sponsors" + } + } + } + }, + "chrome.donate.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Donate via GitHub Sponsors" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поддержать через GitHub Sponsors" + } + } + } + }, + "command.openActivity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть активность" + } + } + } + }, + "command.openDiscover": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Discover" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть каталог" + } + } + } + }, + "command.openLibrary": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Library" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть библиотеку" + } + } + } + }, + "command.openSnapshots": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Snapshots" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть снимки" + } + } + } + }, + "command.openTrending": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Trending" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть тренды" + } + } + } + }, + "command.refreshLibrary": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh Library" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить библиотеку" + } + } + } + }, + "command.refreshSnapshots": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh Snapshots" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить снимки" + } + } + } + }, + "command.refreshTrending": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh Trending" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить тренды" + } + } + } + }, + "command.toggleActivityDrawer": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Activity drawer" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать или скрыть журнал активности" + } + } + } + }, + "github.connectedAs": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub: connected as @{username}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "GitHub: выполнен вход как @{username}" + } + } + } + }, + "github.scopeIncomplete": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub: signed in, but scope incomplete — click to fix" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "GitHub: вход выполнен, но не хватает разрешений — нажмите, чтобы исправить" + } + } + } + }, + "keyboard.close": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "close" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "закрыть" + } + } + } + }, + "keyboard.navigate": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "navigate" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "перейти" + } + } + } + }, + "keyboard.open": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "open" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "открыть" + } + } + } + }, + "language.english": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "English" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Английский" + } + } + } + }, + "language.hint.reload": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Changing the language reloads brew-browser." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "После смены языка brew-browser перезагрузится." + } + } + } + }, + "language.label": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Language" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Язык" + } + } + } + }, + "language.russian": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Russian" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Русский" + } + } + } + }, + "language.system": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System language" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Язык системы" + } + } + } + }, + "nav.activity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Активность" + } + } + } + }, + "nav.dashboard": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dashboard" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сводка" + } + } + } + }, + "nav.discover": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Discover" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Каталог" + } + } + } + }, + "nav.library": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Library" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Библиотека" + } + } + } + }, + "nav.primary": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Primary navigation" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Основная навигация" + } + } + } + }, + "nav.services": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Службы" + } + } + } + }, + "nav.snapshots": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Snapshots" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снимки" + } + } + } + }, + "nav.trending": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Trending" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тренды" + } + } + } + }, + "nav.showSidebar": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show sidebar" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать боковую панель" + } + } + } + }, + "nav.hideSidebar": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Hide sidebar" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скрыть боковую панель" + } + } + } + }, + "package.installed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "installed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "установлен" + } + } + } + }, + "package.kind.cask": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cask" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "cask-пакет" + } + } + } + }, + "package.kind.formula": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "formula" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "формула" + } + } + } + }, + "palette.empty": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No results." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ничего не найдено." + } + } + } + }, + "palette.group.actions": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Actions" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Действия" + } + } + } + }, + "palette.group.commands": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Commands" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Команды" + } + } + } + }, + "palette.group.index": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Index" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Каталог" + } + } + } + }, + "palette.group.installed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установленные" + } + } + } + }, + "palette.results": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command palette results" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Результаты палитры команд" + } + } + } + }, + "palette.search.placeholder": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Type a command, package, or section." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Введите команду, пакет или раздел." + } + } + } + }, + "palette.searchLabel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command palette search" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск в палитре команд" + } + } + } + }, + "palette.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command palette" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Палитра команд" + } + } + } + }, + "palette.group.view": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "View" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вид" + } + } + } + }, + "search.allResultsInDiscover": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "See all results in Discover →" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Все результаты в Каталоге →" + } + } + } + }, + "search.clear": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить" + } + } + } + }, + "search.clearSearch": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear search" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить поиск" + } + } + } + }, + "search.noMatches": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No matches for \"{query}\"." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "По запросу «{query}» ничего не найдено." + } + } + } + }, + "search.packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск пакетов" + } + } + } + }, + "search.packagesPlaceholder": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search packages…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск пакетов…" + } + } + } + }, + "search.results": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search results" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Результаты поиска" + } + } + } + }, + "search.searching": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Searching…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Идёт поиск…" + } + } + } + }, + "settings.open": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Settings" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть настройки" + } + } + } + }, + "settings.close": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Settings" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть настройки" + } + } + } + }, + "settings.paneLabel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings — {section}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройки — {section}" + } + } + } + }, + "settings.sections": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings sections" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделы настроек" + } + } + } + }, + "settings.section.about": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О приложении" + } + } + } + }, + "settings.section.activity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Активность" + } + } + } + }, + "settings.section.appearance": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Appearance" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Оформление" + } + } + } + }, + "settings.section.brew": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Brew" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Brew" + } + } + } + }, + "settings.section.github": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + } + } + }, + "settings.section.network": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Network" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сеть" + } + } + } + }, + "settings.shortcutTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings (⌘,)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройки (⌘,)" + } + } + } + }, + "settings.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройки" + } + } + } + }, + "theme.change": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Change theme" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Изменить тему" + } + } + } + }, + "theme.dark": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dark" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тёмная" + } + } + } + }, + "theme.light": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Light" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Светлая" + } + } + } + }, + "theme.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Theme: {theme}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тема: {theme}" + } + } + } + }, + "theme.system": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Системная" + } + } + } + }, + "trending.window.30d": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "30d" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "30 дн." + } + } + } + }, + "trending.window.90d": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "90d" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "90 дн." + } + } + } + }, + "trending.window.365d": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "365d" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "365 дн." + } + } + } + }, + "updates.available": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update available" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Доступно обновление" + } + } + } + }, + "updates.availableLabel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update available: brew-browser {version}. Click to open Settings." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Доступно обновление: brew-browser {version}. Нажмите, чтобы открыть Настройки." + } + } + } + }, + "updates.dismissVersion": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dismiss update notification for v{version}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скрыть уведомление об обновлении до v{version}" + } + } + } + }, + "updates.installing": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installing update" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устанавливаем обновление" + } + } + } + }, + "updates.skipVersion": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Skip this version" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пропустить эту версию" + } + } + } + }, + "updates.versionAvailable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser {version} is available" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Доступна версия brew-browser {version}" + } + } + } + }, + "vulnerability.packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "{count} vulnerable packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уязвимых пакетов: {count}" + } + } + } + }, + "About": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О приложении" + } + } + } + }, + "About Brew Browser": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About Brew Browser" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О Brew Browser" + } + } + } + }, + "Activity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Активность" + } + } + } + }, + "Actions": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Actions" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Действия" + } + } + } + }, + "Advanced": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Advanced" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Дополнительно" + } + } + } + }, + "AI features": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "AI features" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Функции ИИ" + } + } + } + }, + "All": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "All" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Все" + } + } + } + }, + "All Categories": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "All Categories" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Все категории" + } + } + } + }, + "All current": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "All current" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Всё актуально" + } + } + } + }, + "Analytics": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Analytics" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Аналитика" + } + } + } + }, + "Appearance": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Appearance" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Оформление" + } + } + } + }, + "App version": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "App version" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Версия приложения" + } + } + } + }, + "Autoremove": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Autoremove" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить лишние зависимости" + } + } + } + }, + "Autoremove unused dependencies": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Autoremove unused dependencies" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить неиспользуемые зависимости" + } + } + } + }, + "Auto-check daily": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Auto-check daily" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверять ежедневно" + } + } + } + }, + "Body (Markdown)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Body (Markdown)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Текст (Markdown)" + } + } + } + }, + "Brew": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Brew" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Brew" + } + } + } + }, + "Browse": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browse" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обзор" + } + } + } + }, + "Cancel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отмена" + } + } + } + }, + "Casks": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Casks" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Cask-пакеты" + } + } + } + }, + "Casks (Caskroom)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Casks (Caskroom)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Cask-пакеты (Caskroom)" + } + } + } + }, + "Caveats": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Caveats" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Примечания" + } + } + } + }, + "Check for updates now": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check for updates now" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить обновления сейчас" + } + } + } + }, + "Check now": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check now" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить" + } + } + } + }, + "Choose packages to upgrade": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose packages to upgrade" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выберите пакеты для обновления" + } + } + } + }, + "Choose…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выбрать…" + } + } + } + }, + "Categories unavailable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Categories unavailable" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Категории недоступны" + } + } + } + }, + "Categories": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Categories" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Категории" + } + } + } + }, + "Clear": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить" + } + } + } + }, + "Clear completed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear completed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить завершённые" + } + } + } + }, + "Clear Finished": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear Finished" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить завершённые" + } + } + } + }, + "Clear categories": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear categories" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сбросить категории" + } + } + } + }, + "Clear category filter": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear category filter" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сбросить фильтр категорий" + } + } + } + }, + "Clear filter": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear filter" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сбросить фильтр" + } + } + } + }, + "Click the code to copy it.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Click the code to copy it." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нажмите код, чтобы скопировать его." + } + } + } + }, + "Close": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть" + } + } + } + }, + "Command Palette…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command Palette…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Палитра команд…" + } + } + } + }, + "Composition": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Composition" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Состав" + } + } + } + }, + "Confirm before uninstall / zap": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Confirm before uninstall / zap" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подтверждать удаление и полную очистку (zap)" + } + } + } + }, + "Confirmations": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Confirmations" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подтверждения" + } + } + } + }, + "Contacting GitHub…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Contacting GitHub…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Связываемся с GitHub…" + } + } + } + }, + "Create": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Create" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Создать" + } + } + } + }, + "Dashboard": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dashboard" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сводка" + } + } + } + }, + "Dark": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dark" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тёмная" + } + } + } + }, + "Default landing": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Default landing" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Стартовый раздел" + } + } + } + }, + "Delete": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Delete" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить" + } + } + } + }, + "Dependency": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dependency" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Зависимость" + } + } + } + }, + "Description": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Description" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Описание" + } + } + } + }, + "Download cache": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Download cache" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Кэш загрузок" + } + } + } + }, + "Deselect all": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deselect all" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снять выбор" + } + } + } + }, + "Discover": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Discover" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Каталог" + } + } + } + }, + "Dismiss": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dismiss" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скрыть" + } + } + } + }, + "Dismiss for this session": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dismiss for this session" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скрыть до конца сеанса" + } + } + } + }, + "Donate to the project": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Donate to the project" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поддержать проект" + } + } + } + }, + "Enter this code:": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter this code:" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Введите этот код:" + } + } + } + }, + "Esc": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Esc" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Esc" + } + } + } + }, + "Export…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Export…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Экспорт…" + } + } + } + }, + "Exposure": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Exposure" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уязвимости" + } + } + } + }, + "Fetch latest categories & descriptions": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Fetch latest categories & descriptions" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновлять категории и описания" + } + } + } + }, + "Fetch trending history": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Fetch trending history" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружать историю трендов" + } + } + } + }, + "File issue": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "File issue" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сообщить о проблеме" + } + } + } + }, + "File an issue": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "File an issue" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сообщить о проблеме" + } + } + } + }, + "Filter": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Filter" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Фильтр" + } + } + } + }, + "Formulae": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Formulae" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Формулы" + } + } + } + }, + "Formulae (Cellar)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Formulae (Cellar)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Формулы (Cellar)" + } + } + } + }, + "GitHub": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + } + } + }, + "Greedy upgrades (include self-updating casks)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Greedy upgrades (include self-updating casks)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновлять также самообновляемые cask-пакеты" + } + } + } + }, + "Homebrew": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Homebrew" + } + } + } + }, + "Icon source": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Icon source" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Источник иконки" + } + } + } + }, + "Import…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Import…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Импорт…" + } + } + } + }, + "Install": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установить" + } + } + } + }, + "Install brew-vulns": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install brew-vulns" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установить brew-vulns" + } + } + } + }, + "Install complete. Relaunch to use the new version.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install complete. Relaunch to use the new version." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установка завершена. Перезапустите приложение, чтобы перейти на новую версию." + } + } + } + }, + "Install trend": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install trend" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Динамика установок" + } + } + } + }, + "Installed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установлено" + } + } + } + }, + "Installed only": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installed only" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Только установленные" + } + } + } + }, + "Installing…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installing…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устанавливаем…" + } + } + } + }, + "instead": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "instead" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "вместо" + } + } + } + }, + "Keep last N completed jobs": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Keep last N completed jobs" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Хранить последние N завершённых задач" + } + } + } + }, + "Language": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Language" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Язык" + } + } + } + }, + "Labels": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Labels" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Метки" + } + } + } + }, + "Latest": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Latest" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Актуальная версия" + } + } + } + }, + "Library": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Library" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Библиотека" + } + } + } + }, + "License": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "License" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Лицензия" + } + } + } + }, + "License mismatch": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "License mismatch" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Лицензии не совпадают" + } + } + } + }, + "Light": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Light" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Светлая" + } + } + } + }, + "Lines per job": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Lines per job" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Строк на задачу" + } + } + } + }, + "Loading GitHub stats…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading GitHub stats…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем статистику GitHub…" + } + } + } + }, + "Loading settings…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading settings…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем настройки…" + } + } + } + }, + "Loading sign-in status…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading sign-in status…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверяем вход…" + } + } + } + }, + "Logs (var/log)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Logs (var/log)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Логи (var/log)" + } + } + } + }, + "Manual": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Manual" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вручную" + } + } + } + }, + "Name": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Name" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Имя" + } + } + } + }, + "Network": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Network" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сеть" + } + } + } + }, + "New Snapshot": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Snapshot" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новый снимок" + } + } + } + }, + "No beta channel in this release.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No beta channel in this release." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "В этом релизе нет бета-канала." + } + } + } + }, + "No background services": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No background services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Фоновых служб нет" + } + } + } + }, + "No known vulnerabilities.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No known vulnerabilities." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Известных уязвимостей нет." + } + } + } + }, + "No known vulnerabilities": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No known vulnerabilities" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Известных уязвимостей нет" + } + } + } + }, + "No outdated packages.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No outdated packages." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устаревших пакетов нет." + } + } + } + }, + "No results.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No results." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ничего не найдено." + } + } + } + }, + "No snapshots yet": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No snapshots yet" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снимков пока нет" + } + } + } + }, + "Not enough data yet": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not enough data yet" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пока недостаточно данных" + } + } + } + }, + "Not scanned yet.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not scanned yet." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверка ещё не выполнялась." + } + } + } + }, + "Offline Mode": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим" + } + } + } + }, + "Off": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Off" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выкл." + } + } + } + }, + "OK": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + } + } + }, + "Open in browser": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open in browser" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть в браузере" + } + } + } + }, + "Open in Finder": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open in Finder" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать в Finder" + } + } + } + }, + "Outbound paths (read-only)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Outbound paths (read-only)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Исходящие сетевые обращения (только чтение)" + } + } + } + }, + "Package detail": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Package detail" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Карточка пакета" + } + } + } + }, + "Patched in": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Patched in" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Исправлено в" + } + } + } + }, + "Pinned": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pinned" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закреплён" + } + } + } + }, + "Pinned packages are not upgraded by brew. Unpin from the package detail page.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pinned packages are not upgraded by brew. Unpin from the package detail page." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закреплённые пакеты не обновляются через brew. Открепите пакет в его карточке." + } + } + } + }, + "Recent": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Recent" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Недавние" + } + } + } + }, + "RECENT": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "RECENT" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "НЕДАВНИЕ" + } + } + } + }, + "Refresh": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить" + } + } + } + }, + "Refresh from brew.sh →": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh from brew.sh →" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить с brew.sh →" + } + } + } + }, + "Refreshing…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refreshing…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновляем…" + } + } + } + }, + "Remove": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Убрать" + } + } + } + }, + "Repository": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Repository" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Репозиторий" + } + } + } + }, + "Reset to defaults": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reset to defaults" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сбросить настройки" + } + } + } + }, + "Resize package detail panel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Resize package detail panel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Изменить ширину карточки пакета" + } + } + } + }, + "Restore": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restore" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Восстановить" + } + } + } + }, + "Retry": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Retry" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Повторить" + } + } + } + }, + "Re-scan all": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Re-scan all" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить всё заново" + } + } + } + }, + "Scan all packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scan all packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить все пакеты" + } + } + } + }, + "Scan installed packages for known vulnerabilities": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scan installed packages for known vulnerabilities" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверять установленные пакеты на известные уязвимости" + } + } + } + }, + "Scan now": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scan now" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить сейчас" + } + } + } + }, + "Scanning all…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scanning all…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверяем все пакеты…" + } + } + } + }, + "Scanning…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scanning…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверяем…" + } + } + } + }, + "Scrub — also remove the latest versions' cached downloads (more aggressive)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scrub — also remove the latest versions' cached downloads (more aggressive)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тщательная очистка: удалить также кэш загрузок последних версий (агрессивнее)" + } + } + } + }, + "Search": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск" + } + } + } + }, + "Search packages…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search packages…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск пакетов…" + } + } + } + }, + "Searching…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Searching…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Идёт поиск…" + } + } + } + }, + "Security": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Security" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Безопасность" + } + } + } + }, + "Select all": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Select all" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выбрать все" + } + } + } + }, + "Send Homebrew install analytics": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Send Homebrew install analytics" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отправлять аналитику установок Homebrew" + } + } + } + }, + "Service": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Service" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Служба" + } + } + } + }, + "Services": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Службы" + } + } + } + }, + "Settings": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройки" + } + } + } + }, + "Settings file is unreadable.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings file is unreadable." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Файл настроек не читается." + } + } + } + }, + "Show GitHub stats on package pages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show GitHub stats on package pages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывать статистику GitHub на страницах пакетов" + } + } + } + }, + "Show sidebar": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show sidebar" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать боковую панель" + } + } + } + }, + "Hide sidebar": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Hide sidebar" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скрыть боковую панель" + } + } + } + }, + "Sign in": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вход" + } + } + } + }, + "Sign in to GitHub": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in to GitHub" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Войти в GitHub" + } + } + } + }, + "Sign in with GitHub": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in with GitHub" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Войти через GitHub" + } + } + } + }, + "Sign out": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign out" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выйти" + } + } + } + }, + "Sign-in denied.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign-in denied." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вход отклонён." + } + } + } + }, + "Similar packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Similar packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Похожие пакеты" + } + } + } + }, + "Size": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Size" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Размер" + } + } + } + }, + "Snapshot": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Snapshot" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снимок" + } + } + } + }, + "Snapshots": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Snapshots" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снимки" + } + } + } + }, + "Stable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stable" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Стабильный" + } + } + } + }, + "Start": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Start" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запустить" + } + } + } + }, + "Stop": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Остановить" + } + } + } + }, + "Storage": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Storage" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Хранилище" + } + } + } + }, + "Status": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Status" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Статус" + } + } + } + }, + "Submit": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Submit" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отправить" + } + } + } + }, + "Support brew-browser via GitHub Sponsors": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Support brew-browser via GitHub Sponsors" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поддержать brew-browser через GitHub Sponsors" + } + } + } + }, + "System": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Системная" + } + } + } + }, + "System language": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System language" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Язык системы" + } + } + } + }, + "Tags": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tags" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Теги" + } + } + } + }, + "Tap": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tap" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Tap" + } + } + } + }, + "Terminal": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Terminal" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Терминал" + } + } + } + }, + "Theme": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Theme" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тема" + } + } + } + }, + "Title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Title" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Заголовок" + } + } + } + }, + "Token": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Token" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Токен" + } + } + } + }, + "Top categories in your library": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Top categories in your library" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Главные категории в библиотеке" + } + } + } + }, + "Trending": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Trending" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тренды" + } + } + } + }, + "Trending cache TTL": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Trending cache TTL" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "TTL кэша трендов" + } + } + } + }, + "Try again": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Try again" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Попробовать снова" + } + } + } + }, + "Type": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Type" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тип" + } + } + } + }, + "Type a command, package, or section.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Type a command, package, or section." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Введите команду, пакет или раздел." + } + } + } + }, + "Update available": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update available" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Доступно обновление" + } + } + } + }, + "Update channel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update channel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Канал обновлений" + } + } + } + }, + "Updates": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Updates" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновления" + } + } + } + }, + "Updates available": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Updates available" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Доступны обновления" + } + } + } + }, + "Vulnerable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vulnerable" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уязвимые" + } + } + } + }, + "Velocity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Velocity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Динамика" + } + } + } + }, + "Upgrade": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgrade" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить" + } + } + } + }, + "Upgrade all": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgrade all" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить всё" + } + } + } + }, + "Upgrade available": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgrade available" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Доступно обновление" + } + } + } + }, + "Upgrade to fix": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgrade to fix" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить, чтобы исправить" + } + } + } + }, + "Version": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Version" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Версия" + } + } + } + }, + "Verbose — list every file removed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Verbose — list every file removed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подробный вывод: перечислить все удаляемые файлы" + } + } + } + }, + "View vulnerable packages →": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "View vulnerable packages →" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать уязвимые пакеты →" + } + } + } + }, + "Waiting for authorization…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Waiting for authorization…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ожидаем авторизации…" + } + } + } + }, + "Watch": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Watch" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отслеживать" + } + } + } + }, + "Watching": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Watching" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отслеживается" + } + } + } + }, + "Why install this?": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Why install this?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Зачем это устанавливать?" + } + } + } + }, + "Window": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Window" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Окно" + } + } + } + }, + "Window vibrancy": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Window vibrancy" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Эффект прозрачности окна" + } + } + } + }, + "— all enrichment is baked into the app binary.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "— all enrichment is baked into the app binary." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "— всё уже включено в приложение." + } + } + } + }, + "Zero LLM calls are made from your machine": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Zero LLM calls are made from your machine" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "С вашего компьютера не выполняются LLM-запросы" + } + } + } + }, + "Zero telemetry. Zero accounts.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Zero telemetry. Zero accounts." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Никакой телеметрии. Никаких учётных записей." + } + } + } + }, + "built with": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "built with" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "создано с помощью" + } + } + } + }, + "close": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "close" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "закрыть" + } + } + } + }, + "close Esc": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "close Esc" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть (Esc)" + } + } + } + }, + "dependency": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "dependency" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "зависимость" + } + } + } + }, + "formula": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "formula" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "формула" + } + } + } + }, + "cask": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cask" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "cask" + } + } + } + }, + "installed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "installed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "установлено" + } + } + } + }, + "not present": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "not present" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "нет на диске" + } + } + } + }, + "pinned": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "pinned" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "закреплён" + } + } + } + }, + "source": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "source" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "источник" + } + } + } + }, + "unavailable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "unavailable" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "недоступно" + } + } + } + }, + "working…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "working…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "выполняется…" + } + } + } + }, + "Adopt existing": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Adopt existing" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Использовать существующую копию" + } + } + } + }, + "Backend not available": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Backend not available" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Бэкенд недоступен" + } + } + } + }, + "Catalog refresh failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Catalog refresh failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось обновить каталог" + } + } + } + }, + "Change theme": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Change theme" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Изменить тему" + } + } + } + }, + "Changing the language reloads brew-browser.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Changing the language reloads brew-browser." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "После смены языка brew-browser перезагрузится." + } + } + } + }, + "Cleanup failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cleanup failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось очистить кэш" + } + } + } + }, + "Clean up cache…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clean up cache…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить кэш…" + } + } + } + }, + "Clean up": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clean up" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить" + } + } + } + }, + "Cleaning…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cleaning…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очищаем…" + } + } + } + }, + "Cleaning up Homebrew cache": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cleaning up Homebrew cache" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очищаем кэш Homebrew" + } + } + } + }, + "Command palette": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command palette" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Палитра команд" + } + } + } + }, + "Command palette search": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command palette search" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск в палитре команд" + } + } + } + }, + "Command palette results": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command palette results" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Результаты палитры команд" + } + } + } + }, + "Couldn't change brew analytics": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't change brew analytics" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось изменить аналитику brew" + } + } + } + }, + "Couldn't load services": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't load services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить службы" + } + } + } + }, + "Couldn't load snapshots": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't load snapshots" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить снимки" + } + } + } + }, + "Couldn't load packages.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't load packages." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить пакеты." + } + } + } + }, + "Couldn't open in file manager": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't open in file manager" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось открыть в файловом менеджере" + } + } + } + }, + "Couldn't reveal in Finder": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't reveal in Finder" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось показать в Finder" + } + } + } + }, + "Disk probe failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disk probe failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось проверить диск" + } + } + } + }, + "Donate via GitHub Sponsors": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Donate via GitHub Sponsors" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поддержать через GitHub Sponsors" + } + } + } + }, + "Donate to brew-browser on GitHub Sponsors": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Donate to brew-browser on GitHub Sponsors" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поддержать brew-browser через GitHub Sponsors" + } + } + } + }, + "Everything is up to date": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Everything is up to date" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Всё актуально" + } + } + } + }, + "English": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "English" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Английский" + } + } + } + }, + "Follows the macOS theme when set to System.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Follows the macOS theme when set to System." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Следует теме macOS, если выбрана системная тема." + } + } + } + }, + "Failed to load packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Failed to load packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить пакеты" + } + } + } + }, + "Force remove": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Force remove" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить принудительно" + } + } + } + }, + "GitHub connection status": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub connection status" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Статус подключения GitHub" + } + } + } + }, + "Homebrew analytics disabled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew analytics disabled" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Аналитика Homebrew отключена" + } + } + } + }, + "Homebrew analytics enabled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew analytics enabled" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Аналитика Homebrew включена" + } + } + } + }, + "Homebrew was not found on this Mac.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew was not found on this Mac." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Homebrew не найден на этом Mac." + } + } + } + }, + "Index": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Index" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Индекс" + } + } + } + }, + "Installed as a dependency, not requested directly": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installed as a dependency, not requested directly" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установлено как зависимость, а не вручную" + } + } + } + }, + "Invalid URL": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Invalid URL" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Недопустимый URL" + } + } + } + }, + "Loading your packages…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading your packages…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем пакеты…" + } + } + } + }, + "Measuring disk usage…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Measuring disk usage…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Считаем место на диске…" + } + } + } + }, + "Nothing's run yet.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing's run yet." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пока ничего не запускалось." + } + } + } + }, + "Open Activity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть активность" + } + } + } + }, + "Open Discover": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Discover" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть каталог" + } + } + } + }, + "Open Library": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Library" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть библиотеку" + } + } + } + }, + "Open Settings": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Settings" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть настройки" + } + } + } + }, + "Open Snapshots": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Snapshots" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть снимки" + } + } + } + }, + "Open Trending": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Trending" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть тренды" + } + } + } + }, + "Open Library, filter outdated": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Library, filter outdated" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть библиотеку с фильтром устаревших" + } + } + } + }, + "Other": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Other" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Другое" + } + } + } + }, + "Pick which packages to upgrade": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pick which packages to upgrade" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выбрать пакеты для обновления" + } + } + } + }, + "Refresh Library": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh Library" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить библиотеку" + } + } + } + }, + "Refresh Snapshots": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh Snapshots" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить снимки" + } + } + } + }, + "Refresh Trending": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh Trending" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить тренды" + } + } + } + }, + "Refresh disk usage": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh disk usage" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить сведения о диске" + } + } + } + }, + "Refreshed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refreshed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновлено" + } + } + } + }, + "Report an issue on GitHub": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Report an issue on GitHub" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сообщить о проблеме на GitHub" + } + } + } + }, + "Report to brew-browser": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Report to brew-browser" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отправить отчёт в brew-browser" + } + } + } + }, + "Remove from history": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove from history" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Убрать из истории" + } + } + } + }, + "Remove it anyway, ignoring packages that depend on it.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove it anyway, ignoring packages that depend on it." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить принудительно, игнорируя пакеты, которые от него зависят." + } + } + } + }, + "Remove unused dependencies?": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove unused dependencies?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить неиспользуемые зависимости?" + } + } + } + }, + "Removing unused dependencies": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Removing unused dependencies" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удаляем неиспользуемые зависимости" + } + } + } + }, + "Removing…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Removing…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удаляем…" + } + } + } + }, + "Replace the existing copy with a fresh Homebrew install.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Replace the existing copy with a fresh Homebrew install." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Заменить существующую копию новой установкой из Homebrew." + } + } + } + }, + "Run brew doctor": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Run brew doctor" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запустить brew doctor" + } + } + } + }, + "Run brew doctor and stream the diagnostics into Activity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Run brew doctor and stream the diagnostics into Activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запустить brew doctor и вывести диагностику в панель активности" + } + } + } + }, + "Run brew update, refresh the catalog from formulae.brew.sh, and reload your installed list": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Run brew update, refresh the catalog from formulae.brew.sh, and reload your installed list" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запустить brew update, обновить каталог с formulae.brew.sh и перечитать список установленных пакетов" + } + } + } + }, + "Russian": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Russian" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Русский" + } + } + } + }, + "Search packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск пакетов" + } + } + } + }, + "Search results": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search results" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Результаты поиска" + } + } + } + }, + "See the Activity drawer.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "See the Activity drawer." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подробности в панели Активность." + } + } + } + }, + "Settings (⌘,)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings (⌘,)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройки (⌘,)" + } + } + } + }, + "Settings sections": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings sections" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделы настроек" + } + } + } + }, + "Submit issue": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Submit issue" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отправить проблему" + } + } + } + }, + "This will install packages from the snapshot. Existing packages are skipped.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will install packages from the snapshot. Existing packages are skipped." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Будут установлены пакеты из снимка. Уже установленные пакеты пропускаются." + } + } + } + }, + "Toggle Activity drawer": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Activity drawer" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать или скрыть журнал активности" + } + } + } + }, + "Upgrade-all failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgrade-all failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось обновить всё" + } + } + } + }, + "Upgrading…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgrading…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновляем…" + } + } + } + }, + "When off, only Homebrew's native metadata appears. Categories (sidebar tile grid, donut chart, chip filters) are also hidden because they're AI-generated.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "When off, only Homebrew's native metadata appears. Categories (sidebar tile grid, donut chart, chip filters) are also hidden because they're AI-generated." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Если выключено, отображаются только штатные метаданные Homebrew. Категории (плитки в боковой панели, кольцевая диаграмма и фильтры по категориям) тоже скрываются, потому что они сгенерированы ИИ." + } + } + } + }, + "When on, brew-browser shows extra metadata generated at build time by AI: friendly names, expanded descriptions, use cases, similar package suggestions, and category tags.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "When on, brew-browser shows extra metadata generated at build time by AI: friendly names, expanded descriptions, use cases, similar package suggestions, and category tags." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Если включено, brew-browser показывает дополнительные метаданные, сгенерированные ИИ при сборке: понятные названия, расширенные описания, сценарии использования, похожие пакеты и теги категорий." + } + } + } + }, + "Which section opens when you launch brew-browser.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Which section opens when you launch brew-browser." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Какой раздел открывается при запуске brew-browser." + } + } + } + }, + "Requires app restart to take effect. The default (HudWindow) matches the rest of macOS.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Requires app restart to take effect. The default (HudWindow) matches the rest of macOS." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вступит в силу после перезапуска приложения. Значение по умолчанию (HudWindow) соответствует остальной macOS." + } + } + } + }, + "all packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "all packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "все пакеты" + } + } + } + }, + "brew commands kicked off from here will show up in this list.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew commands kicked off from here will show up in this list." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Команды brew, запущенные из приложения, появятся в этом списке." + } + } + } + }, + "disabled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "disabled" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "отключено" + } + } + } + }, + "Advisory": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Advisory" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Бюллетень безопасности" + } + } + } + }, + "Catalog unavailable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Catalog unavailable" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Каталог недоступен" + } + } + } + }, + "Check this package for known vulnerabilities.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check this package for known vulnerabilities." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить этот пакет на известные уязвимости." + } + } + } + }, + "Commands": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Commands" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Команды" + } + } + } + }, + "Couldn't load packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't load packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить пакеты" + } + } + } + }, + "Couldn't load trending": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't load trending" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить тренды" + } + } + } + }, + "Copy": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Copy" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скопировать" + } + } + } + }, + "Copy the command to the clipboard": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Copy the command to the clipboard" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скопировать команду в буфер обмена" + } + } + } + }, + "Deprecated": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deprecated" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устарело" + } + } + } + }, + "Disabled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disabled" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отключено" + } + } + } + }, + "Disabled by Offline Mode": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disabled by Offline Mode" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отключено офлайн-режимом" + } + } + } + }, + "Blocked by Offline Mode. Disable in Settings → Network.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Blocked by Offline Mode. Disable in Settings → Network." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Заблокировано офлайн-режимом. Отключите его в разделе «Настройки → Сеть»." + } + } + } + }, + "Fetching install counts from formulae.brew.sh…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Fetching install counts from formulae.brew.sh…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем счётчики установок с formulae.brew.sh…" + } + } + } + }, + "Homebrew isn't installed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew isn't installed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Homebrew не установлен" + } + } + } + }, + "Install Homebrew": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install Homebrew" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установить Homebrew" + } + } + } + }, + "Install `brew vulns` to scan this package for known vulnerabilities.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install `brew vulns` to scan this package for known vulnerabilities." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установите `brew vulns`, чтобы проверить этот пакет на известные уязвимости." + } + } + } + }, + "Install the Xcode Command Line Tools": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install the Xcode Command Line Tools" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установить Xcode Command Line Tools" + } + } + } + }, + "Installs": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installs" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установки" + } + } + } + }, + "Installs, upgrades, uninstalls, and updates show up here.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installs, upgrades, uninstalls, and updates show up here." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Здесь отображаются установки, обновления пакетов, удаления и обновления Homebrew." + } + } + } + }, + "Loading snapshots…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading snapshots…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем снимки…" + } + } + } + }, + "Loading the Homebrew catalog…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading the Homebrew catalog…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем каталог Homebrew…" + } + } + } + }, + "Members also categorized here": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Members also categorized here" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пакеты, которые также входят в эту категорию" + } + } + } + }, + "Members of this category with no other category": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Members of this category with no other category" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пакеты только из этой категории" + } + } + } + }, + "No activity yet": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No activity yet" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Активности пока нет" + } + } + } + }, + "No packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пакетов нет" + } + } + } + }, + "No results": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No results" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ничего не найдено" + } + } + } + }, + "No summary available — view details": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No summary available — view details" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нет краткого описания — открыть подробности" + } + } + } + }, + "Not installed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not installed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не установлено" + } + } + } + }, + "Offline Mode is on — trending needs formulae.brew.sh.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode is on — trending needs formulae.brew.sh." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим включён — трендам нужен доступ к formulae.brew.sh." + } + } + } + }, + "Open Dashboard": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Dashboard" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть сводку" + } + } + } + }, + "Open Services": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть службы" + } + } + } + }, + "Open Terminal": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Terminal" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть Терминал" + } + } + } + }, + "Outdated": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Outdated" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Есть обновления" + } + } + } + }, + "Paste the official install command (from brew.sh) into Terminal:": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Paste the official install command (from brew.sh) into Terminal:" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вставьте официальную команду установки с brew.sh в Терминал:" + } + } + } + }, + "Reading your Homebrew install…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reading your Homebrew install…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Читаем конфигурацию Homebrew…" + } + } + } + }, + "Restart": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезапустить" + } + } + } + }, + "Runs brew vulns across your whole install — it can't scan a single package": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Runs brew vulns across your whole install — it can't scan a single package" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запускает brew vulns для всей установки Homebrew; отдельный пакет проверить нельзя" + } + } + } + }, + "Save your current setup so you can restore it on another Mac. Snapshots live in ~/Library/Application Support/brew-browser/brewfiles/ — findable outside the app too.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Save your current setup so you can restore it on another Mac. Snapshots live in ~/Library/Application Support/brew-browser/brewfiles/ — findable outside the app too." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сохраните текущую конфигурацию, чтобы восстановить её на другом Mac. Снимки лежат в ~/Library/Application Support/brew-browser/brewfiles/ — их можно найти и вне приложения." + } + } + } + }, + "Scanning all packages…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scanning all packages…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверяем все пакеты…" + } + } + } + }, + "Sign in to GitHub happens on first action.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in to GitHub happens on first action." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вход в GitHub откроется при первом действии." + } + } + } + }, + "Sign in to GitHub to star this repository": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in to GitHub to star this repository" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Войдите в GitHub, чтобы поставить звезду репозиторию" + } + } + } + }, + "Sign in to GitHub to watch this repository": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in to GitHub to watch this repository" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Войдите в GitHub, чтобы отслеживать репозиторий" + } + } + } + }, + "Snapshot error": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Snapshot error" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ошибка снимка" + } + } + } + }, + "Star": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Star" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поставить звезду" + } + } + } + }, + "Star this repository": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Star this repository" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поставить звезду репозиторию" + } + } + } + }, + "Starred": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Starred" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Звезда поставлена" + } + } + } + }, + "Stop watching": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop watching" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не отслеживать" + } + } + } + }, + "Stored in ~/Library/Application Support/brew-browser/brewfiles/": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stored in ~/Library/Application Support/brew-browser/brewfiles/" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Хранится в ~/Library/Application Support/brew-browser/brewfiles/" + } + } + } + }, + "The Brewfile will be removed from disk. This cannot be undone.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The Brewfile will be removed from disk. This cannot be undone." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Brewfile будет удалён с диска. Это действие нельзя отменить." + } + } + } + }, + "The Homebrew installer needs them. Run this in Terminal and follow the prompt:": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The Homebrew installer needs them. Run this in Terminal and follow the prompt:" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Они нужны установщику Homebrew. Выполните это в Терминале и следуйте подсказкам:" + } + } + } + }, + "The bundled package catalog couldn't be loaded.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The bundled package catalog couldn't be loaded." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить встроенный каталог пакетов." + } + } + } + }, + "The brew-vulns subcommand isn't installed.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The brew-vulns subcommand isn't installed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подкоманда brew-vulns не установлена." + } + } + } + }, + "settings.vulnerabilities.cleanResult": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No known vulnerabilities across your installed packages." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "В установленных пакетах известные уязвимости не найдены." + } + } + } + }, + "settings.vulnerabilities.helperInstalledTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-vulns installed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-vulns установлен" + } + } + } + }, + "settings.vulnerabilities.initialScanBody": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Running an initial scan now…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запускаем первичную проверку…" + } + } + } + }, + "settings.vulnerabilities.installFailedTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't install brew-vulns" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось установить brew-vulns" + } + } + } + }, + "settings.vulnerabilities.installPrompt": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vulnerability scanning needs the official brew vulns subcommand. Install it now? This runs brew install homebrew/brew-vulns/brew-vulns." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Для проверки уязвимостей нужна официальная подкоманда brew vulns. Установить её сейчас? Будет выполнено brew install homebrew/brew-vulns/brew-vulns." + } + } + } + }, + "settings.vulnerabilities.lastScan": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Last scan" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Последняя проверка" + } + } + } + }, + "settings.vulnerabilities.offlineHint": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode is on — vulnerability scanning is suppressed even if this toggle is on. Disable Offline Mode above to enable scanning." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим включён — проверка уязвимостей заблокирована, даже если переключатель включён. Отключите офлайн-режим выше, чтобы разрешить проверку." + } + } + } + }, + "settings.vulnerabilities.privacyHint": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Opt-in, off by default. When on, brew-browser shells out to the official brew vulns subcommand (Homebrew project), which queries OSV.dev (operated by Google) for known vulnerabilities affecting your installed formulae. If you're also signed in to GitHub, individual GHSA-IDs are enriched with details from api.github.com. Findings are cached locally; no package list leaves your machine except the queries brew vulns itself makes to OSV." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Опционально, выключено по умолчанию. При включении brew-browser запускает официальную подкоманду brew vulns проекта Homebrew: она запрашивает OSV.dev (сервис Google) об известных уязвимостях в установленных формулах. Если вы также вошли в GitHub, отдельные GHSA-ID дополняются сведениями из api.github.com. Результаты кэшируются локально; список пакетов не покидает ваш компьютер, кроме запросов, которые сама brew vulns отправляет в OSV." + } + } + } + }, + "settings.vulnerabilities.scanAllTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Run brew vulns against every installed formula" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запустить brew vulns для всех установленных формул" + } + } + } + }, + "This installs packages from the snapshot. Existing packages are skipped.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This installs packages from the snapshot. Existing packages are skipped." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Будут установлены пакеты из снимка. Уже установленные пакеты пропускаются." + } + } + } + }, + "Use": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Используйте" + } + } + } + }, + "User": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "User" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пользователь" + } + } + } + }, + "Uninstall": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Uninstall" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить" + } + } + } + }, + "Unstar this repository": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Unstar this repository" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Убрать звезду с репозитория" + } + } + } + }, + "Vulnerability Scanning": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vulnerability Scanning" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверка уязвимостей" + } + } + } + }, + "Waiting for Homebrew… the app continues automatically once it's installed.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Waiting for Homebrew… the app continues automatically once it's installed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ожидаем Homebrew… приложение продолжит работу автоматически после установки." + } + } + } + }, + "Watch for activity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Watch for activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отслеживать активность" + } + } + } + }, + "Working… (see Activity)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Working… (see Activity)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выполняется… см. «Активность»" + } + } + } + }, + "Your browser opened to GitHub's device page. Enter this code (it's copied to your clipboard):": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Your browser opened to GitHub's device page. Enter this code (it's copied to your clipboard):" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Браузер открыл страницу GitHub для ввода кода устройства. Введите этот код (он уже скопирован в буфер обмена):" + } + } + } + }, + "brew-browser hit an unexpected error running this command.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser hit an unexpected error running this command." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-browser столкнулся с неожиданной ошибкой при запуске этой команды." + } + } + } + }, + "brew-browser is a window onto Homebrew — install it and the app takes it from there.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser is a window onto Homebrew — install it and the app takes it from there." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-browser работает поверх Homebrew: установите Homebrew, а дальше приложение всё подхватит." + } + } + } + }, + "formulae.brew.sh returned no entries.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "formulae.brew.sh returned no entries." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "formulae.brew.sh не вернул записей." + } + } + } + }, + "instead.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "instead." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "вместо него." + } + } + } + }, + "Already running": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Already running" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уже запущено" + } + } + } + }, + "App Store Version Detected": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "App Store Version Detected" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обнаружена версия из App Store" + } + } + } + }, + "Cancel sign in": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel sign in" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отменить вход" + } + } + } + }, + "Click to copy": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Click to copy" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нажмите, чтобы скопировать" + } + } + } + }, + "Code copied to clipboard": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Code copied to clipboard" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Код скопирован в буфер обмена" + } + } + } + }, + "Code expired. Please try again.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Code expired. Please try again." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Срок действия кода истёк. Попробуйте ещё раз." + } + } + } + }, + "Couldn't copy code": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't copy code" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось скопировать код" + } + } + } + }, + "Delete failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Delete failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось удалить" + } + } + } + }, + "Deleted snapshot": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deleted snapshot" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снимок удалён" + } + } + } + }, + "Export failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Export failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось экспортировать" + } + } + } + }, + "Exported snapshot": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Exported snapshot" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снимок экспортирован" + } + } + } + }, + "Import failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Import failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось импортировать" + } + } + } + }, + "Imported snapshot": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Imported snapshot" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снимок импортирован" + } + } + } + }, + "Install & Override": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install & Override" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установить с заменой" + } + } + } + }, + "Installation Blocked": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installation Blocked" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установка заблокирована" + } + } + } + }, + "Loading brew services…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading brew services…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем службы brew…" + } + } + } + }, + "No background services.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No background services." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Фоновых служб нет." + } + } + } + }, + "Install something like postgresql, redis, or nginx and they'll show up here.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install something like postgresql, redis, or nginx and they'll show up here." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установите, например, postgresql, redis или nginx — службы появятся здесь." + } + } + } + }, + "Refresh services": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить службы" + } + } + } + }, + "Restore complete": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restore complete" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Восстановление завершено" + } + } + } + }, + "Restore failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restore failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось восстановить" + } + } + } + }, + "Snapshot failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Snapshot failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось сохранить снимок" + } + } + } + }, + "Snapshot saved": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Snapshot saved" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снимок сохранён" + } + } + } + }, + "Start service": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Start service" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запустить службу" + } + } + } + }, + "Stop service": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop service" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Остановить службу" + } + } + } + }, + "Restart service": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart service" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезапустить службу" + } + } + } + }, + "Not running": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not running" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не запущено" + } + } + } + }, + "Brew services": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Brew services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Службы brew" + } + } + } + }, + "Open package details": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open package details" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть карточку пакета" + } + } + } + }, + "Save your current setup so you can restore it on another machine. Snapshots live in ~/.local/share/brew-browser/brewfiles/ — findable outside the app too.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Save your current setup so you can restore it on another machine. Snapshots live in ~/.local/share/brew-browser/brewfiles/ — findable outside the app too." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сохраните текущую конфигурацию, чтобы восстановить её на другой машине. Снимки лежат в ~/.local/share/brew-browser/brewfiles/ — их можно найти и вне приложения." + } + } + } + }, + "This will remove the package from your system.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will remove the package from your system." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пакет будет удалён из системы." + } + } + } + }, + "Overwrite manual installation?": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Overwrite manual installation?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Заменить ручную установку?" + } + } + } + }, + "An existing version was found in your Applications folder. If you proceed, Homebrew will force-install and overwrite the existing bundle.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "An existing version was found in your Applications folder. If you proceed, Homebrew will force-install and overwrite the existing bundle." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "В папке /Applications уже есть эта версия. Если продолжить, Homebrew принудительно установит пакет и заменит существующее приложение." + } + } + } + }, + "An existing App Store version was found in your Applications folder.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "An existing App Store version was found in your Applications folder." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "В папке /Applications уже есть версия, установленная через App Store." + } + } + } + }, + "App Store bundles are system-protected and cannot be overwritten by Homebrew.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "App Store bundles are system-protected and cannot be overwritten by Homebrew." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Приложения из App Store защищены системой, и Homebrew не сможет заменить их напрямую." + } + } + } + }, + "Resolution: delete the App Store version from /Applications, then install it again via Homebrew.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Resolution: delete the App Store version from /Applications, then install it again via Homebrew." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Решение: удалите из /Applications версию, установленную через App Store, затем установите приложение через Homebrew." + } + } + } + }, + "Use the Sign in with GitHub button in Settings.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use the Sign in with GitHub button in Settings." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Используйте кнопку «Войти через GitHub» в настройках." + } + } + } + }, + "Re-authorize": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Re-authorize" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить разрешения" + } + } + } + }, + "Upgrade failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgrade failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось обновить" + } + } + } + }, + "Upgraded packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgraded packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пакеты обновлены" + } + } + } + }, + "Watching package": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Watching package" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отслеживаем пакет" + } + } + } + }, + "Stopped watching package": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stopped watching package" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отслеживание пакета отключено" + } + } + } + }, + "Starred package": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Starred package" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Звезда поставлена" + } + } + } + }, + "Unstarred package": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Unstarred package" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Звезда убрана" + } + } + } + }, + "Signed in as GitHub user.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Signed in as GitHub user." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выполнен вход в GitHub." + } + } + } + }, + "settings.activity.rangeDefault": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Range {min}-{max}. Default {default}." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Диапазон {min}–{max}. По умолчанию {default}." + } + } + } + }, + "settings.activity.retentionNote": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "These limits apply to future job persistence. Existing retained data is not trimmed retroactively." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новые ограничения применяются только к задачам, которые будут сохранены после изменения настройки. Уже сохранённая история не сокращается задним числом." + } + } + } + }, + "settings.brew.analyticsDesc.beforeCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew sends anonymous install analytics to formulae.brew.sh by default. This toggle flips Homebrew's setting (the same as running " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Homebrew по умолчанию отправляет анонимную аналитику установок на formulae.brew.sh. Этот переключатель меняет настройку Homebrew (то же, что выполнить " + } + } + } + }, + "settings.brew.analyticsDesc.afterCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " in your terminal)." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " в Терминале)." + } + } + } + }, + "settings.brew.analyticsToggle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Send Homebrew install analytics" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отправлять аналитику установок Homebrew" + } + } + } + }, + "settings.brew.confirmationsDesc": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Destructive actions (Uninstall, Zap, Delete Brewfile) ask before proceeding. Power users can turn this off once they're sure." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перед необратимыми действиями (удаление пакета, zap-очистка, удаление Brewfile) приложение запрашивает подтверждение. Опытные пользователи могут отключить это, когда будут уверены." + } + } + } + }, + "settings.brew.advancedDesc.beforeCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Extra " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Дополнительные параметры " + } + } + } + }, + "settings.brew.advancedDesc.afterCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " options for power users." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " для опытных пользователей." + } + } + } + }, + "settings.brew.greedyDesc.beforeFlag": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Adds " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Добавляет " + } + } + } + }, + "settings.brew.greedyDesc.between": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " to " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " к " + } + } + } + }, + "settings.brew.greedyDesc.afterCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " so casks that update themselves (like Chrome) are upgraded too. Off by default - greedy can churn apps that manage their own updates." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": ", чтобы обновлялись и cask-пакеты с собственным механизмом обновления (например, Chrome). По умолчанию выключено: режим --greedy может лишний раз переустанавливать такие приложения." + } + } + } + }, + "settings.brew.autoremoveDesc.beforeCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Runs " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запускает " + } + } + } + }, + "settings.brew.autoremoveDesc.afterCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " to uninstall formulae that were installed only as dependencies and are no longer needed by anything." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": ", чтобы удалить формулы, установленные только как зависимости и больше не нужные ни одному пакету." + } + } + } + }, + "settings.brew.autoremoveConfirm.beforeCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This runs " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Будет выполнено " + } + } + } + }, + "settings.brew.autoremoveConfirm.afterCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": ", which uninstalls formulae that were installed only as dependencies and are no longer required by anything else." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": ": команда удалит формулы, установленные только как зависимости и больше не используемые другими пакетами." + } + } + } + }, + "settings.liveEnrichment.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Live category & description updates" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Свежие категории и описания" + } + } + } + }, + "settings.liveEnrichment.hint.beforeHost": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser ships with a built-in set of AI categories and descriptions. When on, it refreshes them from " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-browser поставляется со встроенными ИИ-категориями и описаниями. Если включено, приложение обновляет их с " + } + } + } + }, + "settings.liveEnrichment.hint.afterHost": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": ": a tiny version check on refresh, the full category list when it's newer, and a per-package description when you open that package's detail. Only the package name you're viewing is sent (one HTTP GET); no IP is logged at the server, no cookies. Same first-party host as Enhanced Trending - a distinct trust boundary from the always-on Homebrew paths. Requires AI Features on." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": ": при обновлении проверяет версию, загружает полный список категорий, если он новее, и получает описание пакета, когда вы открываете его карточку. Отправляется только имя просматриваемого пакета (один HTTP GET); сервер не логирует IP и не использует файлы cookie. Это собственный эндпоинт проекта brew-browser, отдельная зона доверия от постоянно доступных путей Homebrew. Требует включить функции ИИ." + } + } + } + }, + "settings.liveEnrichment.offline": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode is on - this toggle is locked off. Turn Offline Mode off above to enable live updates." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим включён — переключатель заблокирован. Отключите офлайн-режим выше, чтобы разрешить обновление категорий и описаний." + } + } + } + }, + "settings.trendingHistory.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enhanced Trending History" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Расширенная история трендов" + } + } + } + }, + "settings.trendingHistory.hint.beforeHost": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "When on, brew-browser fetches per-package historical install trends from " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Если включено, brew-browser загружает историческую динамику установок по пакетам с " + } + } + } + }, + "settings.trendingHistory.hint.afterHost": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " to power per-row sparklines on the Trending tab and a chart on each package's detail panel. Only the package name you're viewing is sent (one HTTP GET per package); no IP is logged at the server, no cookies, no fingerprinting. The endpoint is operated by the brew-browser project - a distinct trust boundary from the always-on Homebrew analytics paths above." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " для мини-графиков в строках вкладки «Тренды» и графика в карточке пакета. Отправляется только имя просматриваемого пакета (один HTTP GET на пакет); сервер не логирует IP, не использует файлы cookie и не собирает цифровой отпечаток. Этот эндпоинт поддерживает проект brew-browser — это отдельная зона доверия от постоянно доступных выше путей аналитики Homebrew." + } + } + } + }, + "settings.trendingHistory.offline": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode is on - this toggle is locked off. Turn Offline Mode off above to enable history fetching." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим включён — переключатель заблокирован. Отключите офлайн-режим выше, чтобы разрешить загрузку истории." + } + } + } + }, + "settings.about.zeroTelemetryBody": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser does not collect telemetry. It does not phone home. It does not have user accounts. Every outbound network request is documented in Settings -> Network and the README, and only fires when you take an action that requires it." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-browser не собирает телеметрию, не обращается к своим серверам без необходимости и не использует учётные записи. Все исходящие сетевые запросы описаны в «Настройки → Сеть» и README и выполняются только для действий, которым они нужны." + } + } + } + }, + "about.closeAria": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close About" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть окно «О приложении»" + } + } + } + }, + "about.tagline": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "A native macOS GUI for Homebrew." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нативный интерфейс macOS для Homebrew." + } + } + } + }, + "about.creditsBody.beforeClaude": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " - the multi-agent toolkit that orchestrated the waves (Backend Architect, Frontend Developer, Security Engineer, Code Reviewer, Technical Writer, and friends), powered by " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " — многоагентный набор инструментов, который координировал волны работы (Backend Architect, Frontend Developer, Security Engineer, Code Reviewer, Technical Writer и другие), при участии " + } + } + } + }, + "about.creditsBody.afterClaude": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " in the terminal, running " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " в терминале, модель " + } + } + } + }, + "about.thanksLinux": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Thanks also to the Homebrew project for the package data, every formula maintainer for their work, and Tauri for the native shell." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Благодарим Homebrew за каталог пакетов, мейнтейнеров формул — за их работу, а Tauri — за нативную оболочку." + } + } + } + }, + "about.thanksMac": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Thanks also to the Homebrew project for the package data, every formula and cask maintainer for their work, and Tauri for the native shell." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Благодарим Homebrew за каталог пакетов, мейнтейнеров формул и cask-пакетов — за их работу, а Tauri — за нативную оболочку." + } + } + } + }, + "about.posture": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Zero telemetry. Zero accounts. Every outbound network call is documented in Settings -> Network." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Никакой телеметрии. Никаких учётных записей. Все исходящие сетевые обращения описаны в «Настройки → Сеть»." + } + } + } + }, + "Repo": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Repo" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Репозиторий" + } + } + } + }, + "Built with": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Built with" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Создано с помощью" + } + } + } + }, + "Open GitHub Sponsors in your browser": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open GitHub Sponsors in your browser" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть GitHub Sponsors в браузере" + } + } + } + }, + "Close detail panel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close detail panel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть карточку пакета" + } + } + } + }, + "Loading package detail…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading package detail…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем карточку пакета…" + } + } + } + }, + "Couldn't load detail": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't load detail" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить карточку" + } + } + } + }, + "Installed by User": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installed by User" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Найдено вне Homebrew" + } + } + } + }, + "On request": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "On request" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вручную" + } + } + } + }, + "Total on-disk size of the installed keg (all versions).": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Total on-disk size of the installed keg (all versions)." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Общий размер установленного keg на диске (все версии)." + } + } + } + }, + "About categories": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About categories" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О категориях" + } + } + } + }, + "About this package's categories": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About this package's categories" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О категориях этого пакета" + } + } + } + }, + "About tags": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About tags" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О тегах" + } + } + } + }, + "About these tags": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About these tags" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Об этих тегах" + } + } + } + }, + "About this summary": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About this summary" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Об описании" + } + } + } + }, + "detail.aboutCategories.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if a category looks off and we'll fix it in the next release." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если категория выглядит неверно, и мы исправим её в следующем релизе." + } + } + } + }, + "detail.aboutTags.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if a tag looks off and we'll fix it in the next release." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если тег выглядит неверно, и мы исправим его в следующем релизе." + } + } + } + }, + "detail.aboutSummary.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if the summary looks off and we'll fix it in the next release." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если описание выглядит неверно, и мы исправим его в следующем релизе." + } + } + } + }, + "No longer available via Homebrew.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No longer available via Homebrew." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Больше недоступно через Homebrew." + } + } + } + }, + "May be removed in a future Homebrew update.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "May be removed in a future Homebrew update." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Может быть удалено при одном из будущих обновлений Homebrew." + } + } + } + }, + "Run brew upgrade for this package": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Run brew upgrade for this package" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запустить brew upgrade для этого пакета" + } + } + } + }, + "Open advisory": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open advisory" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть бюллетень безопасности" + } + } + } + }, + "Patched version": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Patched version" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Исправленная версия" + } + } + } + }, + "detail.trend.bootstrap": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Bootstrap + daily snapshots - granularity grows over time" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Начальные и ежедневные снимки — детализация растёт со временем" + } + } + } + }, + "detail.trend.daily": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Daily install snapshots" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ежедневные снимки установок" + } + } + } + }, + "Use cases": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use cases" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сценарии использования" + } + } + } + }, + "About use cases": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About use cases" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О сценариях использования" + } + } + } + }, + "About these use cases": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About these use cases" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Об этих сценариях использования" + } + } + } + }, + "detail.aboutUseCases.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if these use cases look off and we'll fix them in the next release." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если сценарии использования выглядят неверно, и мы исправим их в следующем релизе." + } + } + } + }, + "About similar packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About similar packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О похожих пакетах" + } + } + } + }, + "About these similar packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About these similar packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Об этих похожих пакетах" + } + } + } + }, + "detail.aboutSimilar.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if these suggestions look off and we'll fix them in the next release." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если рекомендации выглядят неверно, и мы исправим их в следующем релизе." + } + } + } + }, + "GitHub repository statistics": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub repository statistics" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Статистика репозитория GitHub" + } + } + } + }, + "Couldn't load GitHub stats": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't load GitHub stats" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить статистику GitHub" + } + } + } + }, + "Dependencies": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dependencies" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Зависимости" + } + } + } + }, + "Required by": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Required by" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Зависимые пакеты" + } + } + } + }, + "About reverse dependencies": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About reverse dependencies" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Об обратных зависимостях" + } + } + } + }, + "detail.aboutReverseDependencies.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Packages in the bundled catalog that list this one as a dependency. Computed offline by inverting the catalog graph - no network or brew calls. Build-only and opt-in (recommended/optional) edges are labelled; everything else is a required runtime dependency." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пакеты во встроенном каталоге, где этот пакет указан как зависимость. Вычисляется офлайн инверсией графа каталога — без сетевых запросов и вызовов brew. Зависимости только для сборки и опциональные/рекомендуемые связи помечены; остальное — обязательные runtime-зависимости." + } + } + } + }, + "Conflicts with": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Conflicts with" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Конфликтует с" + } + } + } + }, + "detail.action.reinstall": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reinstall" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переустановить" + } + } + } + }, + "detail.category.openTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browse all packages in {category}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать все пакеты в категории «{category}»" + } + } + } + }, + "detail.caskUnavailableLinux": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Casks require macOS — this package can't be installed on Linux." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Cask-пакеты требуют macOS — этот пакет нельзя установить в Linux." + } + } + } + }, + "detail.deprecation.disabled.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This package is disabled and is no longer available — it will be removed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Этот пакет отключён и больше недоступен — он будет удалён." + } + } + } + }, + "detail.deprecation.deprecated.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This package is deprecated and scheduled for removal. It still installs for now." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Этот пакет устарел и запланирован к удалению. Пока его ещё можно установить." + } + } + } + }, + "detail.deprecation.disabledDate.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disabled: %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отключён: %@" + } + } + } + }, + "detail.deprecation.deprecatedDate.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deprecated: %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устарел: %@" + } + } + } + }, + "detail.deprecation.disabled.tooltip": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disabled — this package is no longer available and will be removed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отключено — пакет больше недоступен и будет удалён." + } + } + } + }, + "detail.deprecation.deprecated.tooltip": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deprecated — still installable, but scheduled for removal." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устарело — пока устанавливается, но запланировано к удалению." + } + } + } + }, + "detail.deprecation.useInstead": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use instead:" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Используйте вместо него:" + } + } + } + }, + "about.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About Brew Browser" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О Brew Browser" + } + } + } + }, + "action.cancel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отмена" + } + } + } + }, + "action.close": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть" + } + } + } + }, + "action.deselectAll": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deselect all" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снять выбор" + } + } + } + }, + "action.installing": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installing…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устанавливаем…" + } + } + } + }, + "action.refresh": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить" + } + } + } + }, + "action.retry": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Retry" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Повторить" + } + } + } + }, + "action.selectAll": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Select all" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выбрать все" + } + } + } + }, + "activity.collapse": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Collapse activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Свернуть активность" + } + } + } + }, + "activity.copyOutput": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Copy output" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скопировать вывод" + } + } + } + }, + "activity.drawer.hide": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Hide Activity drawer" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скрыть журнал активности" + } + } + } + }, + "activity.drawer.show": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Activity drawer" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать журнал активности" + } + } + } + }, + "activity.expand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Expand activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Развернуть активность" + } + } + } + }, + "activity.keepJobs.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Keep last %d completed jobs" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Хранить последние %d завершённых задач" + } + } + } + }, + "activity.limits.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "These limits apply to future saved jobs only. Existing history is not shortened retroactively." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новые ограничения применяются только к задачам, которые будут сохранены после изменения настройки. Уже сохранённая история не сокращается задним числом." + } + } + } + }, + "activity.linesPerJob.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Keep last %d log lines per job" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Хранить последние %d строк лога на задачу" + } + } + } + }, + "activity.notifyOnCompletion": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Notify when jobs complete" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уведомлять о завершении задач" + } + } + } + }, + "activity.notifyOnCompletion.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows a macOS notification when a long-running brew job finishes." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывает уведомление macOS, когда долгая задача brew завершается." + } + } + } + }, + "chrome.donate.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Support brew-browser via GitHub Sponsors" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поддержать brew-browser через GitHub Sponsors" + } + } + } + }, + "chrome.donate.menu": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sponsor brew-browser…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поддержать brew-browser…" + } + } + } + }, + "icon.source.faviconFrom": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Favicon from {homepage}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Favicon с {homepage}" + } + } + } + }, + "icon.source.homepage": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "homepage" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "домашняя страница" + } + } + } + }, + "icon.source.installedApp": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "installed app" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "установленное приложение" + } + } + } + }, + "icon.source.none": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "none" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "нет" + } + } + } + }, + "enrichment.fetchLatest": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Fetch latest categories & descriptions" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновлять категории и описания" + } + } + } + }, + "enrichment.fetchLatest.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser ships with built-in AI categories and descriptions. When enabled, it checks for newer first-party enrichment data and fetches a package description when you open the package detail. Only the viewed package name is sent." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-browser поставляется со встроенными ИИ-категориями и описаниями. Если включено, приложение проверяет наличие более свежих данных проекта и получает описание пакета, когда вы открываете его карточку. Отправляется только имя просматриваемого пакета." + } + } + } + }, + "filter.all": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "All" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Все" + } + } + } + }, + "filter.dependency": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dependencies" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Зависимости" + } + } + } + }, + "filter.manual": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Manual" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вручную" + } + } + } + }, + "filter.outdated": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Outdated" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Есть обновления" + } + } + } + }, + "filter.vulnerable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vulnerable" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уязвимые" + } + } + } + }, + "github.connectedAs.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub: connected as @%@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "GitHub: выполнен вход как @%@" + } + } + } + }, + "github.deviceFlowInstructions.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open %@ and enter this code. It has been copied to your clipboard." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Откройте %@ и введите этот код. Он уже скопирован в буфер обмена." + } + } + } + }, + "github.keychain.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The token is stored in macOS Keychain. Sign-in is optional and uses the minimum scopes needed for stars, watches, and issues." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Токен хранится в macOS Keychain. Вход необязателен и использует минимальные разрешения для звёзд, отслеживания и создания issue в GitHub." + } + } + } + }, + "github.noInstalledHomepages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "None of your installed packages have a GitHub homepage." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "У установленных пакетов нет домашней страницы GitHub." + } + } + } + }, + "github.signIn": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вход" + } + } + } + }, + "github.signIn.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in with GitHub to star, watch, and file package metadata issues from brew-browser." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Войдите в GitHub, чтобы ставить звёзды, отслеживать репозитории и создавать issue в GitHub по метаданным пакетов из brew-browser." + } + } + } + }, + "github.signInWithGitHub": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in with GitHub" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Войти через GitHub" + } + } + } + }, + "github.signOut": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign out" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выйти" + } + } + } + }, + "github.signedInAs": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Signed in as" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выполнен вход как" + } + } + } + }, + "github.signedInAsToast": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Signed in as @{username}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выполнен вход как @{username}" + } + } + } + }, + "github.signedInToast": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Signed in to GitHub" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выполнен вход в GitHub" + } + } + } + }, + "github.signInDenied": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub sign-in denied" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вход в GitHub отклонён" + } + } + } + }, + "github.signInExpired": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign-in code expired" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Срок действия кода истёк" + } + } + } + }, + "github.signInExpiredBody": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Try again." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Попробуйте ещё раз." + } + } + } + }, + "github.stargazers": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stargazers" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Звёзды" + } + } + } + }, + "github.forks": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Forks" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Форки" + } + } + } + }, + "github.licenseMismatchTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew reports: {brew} · GitHub reports: {github}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew сообщает: {brew} · GitHub сообщает: {github}" + } + } + } + }, + "github.scopeRequired.title.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ needs more access" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нужны дополнительные разрешения: %@" + } + } + } + }, + "github.scopeRequired.body.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Needs the \"%@\" GitHub permission. Click to grant it without signing out." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не хватает разрешения GitHub «%@». Нажмите, чтобы выдать его без выхода из аккаунта." + } + } + } + }, + "github.yourCode": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Your code" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ваш код" + } + } + } + }, + "brew.status.checking": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Checking Homebrew…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверяем Homebrew…" + } + } + } + }, + "brew.status.installed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew is installed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Homebrew установлен." + } + } + } + }, + "brew.status.notFound": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew not found" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew не найден" + } + } + } + }, + "brew.status.notFoundOnPath": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew not found on PATH." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Homebrew не найден в PATH." + } + } + } + }, + "brew.status.ready.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew %@ · prefix %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Homebrew %@ · prefix %@" + } + } + } + }, + "brew.status.unknown": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew status unknown" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Статус Homebrew неизвестен" + } + } + } + }, + "brew.status.unknownWithError": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew status unknown - {error}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Статус Homebrew неизвестен — {error}" + } + } + } + }, + "catalog.refresh.noWritableDir": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't locate a writable catalog directory." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось найти доступный для записи каталог." + } + } + } + }, + "catalog.refresh.failed.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Catalog refresh failed: %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось обновить каталог: %@" + } + } + } + }, + "catalog.refresh.malformedResponse": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew.sh returned an unexpected catalog format." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew.sh вернул каталог в неожиданном формате." + } + } + } + }, + "catalog.refresh.emptyResponse.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew.sh returned an empty catalog (formulae=%d, casks=%d)." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew.sh вернул пустой каталог (формулы=%d, cask-пакеты=%d)." + } + } + } + }, + "catalog.refresh.compression": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't compress the refreshed catalog." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось сжать обновлённый каталог." + } + } + } + }, + "catalog.refresh.offlineBlocked": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline mode is on — catalog refresh is blocked. Disable it in Settings → Network." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим включён — обновление каталога заблокировано. Отключите его в разделе «Настройки → Сеть»." + } + } + } + }, + "catalog.refresh.brewFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew failed during refresh (exit {code})." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew завершился с ошибкой при обновлении каталога (код {code})." + } + } + } + }, + "menu.go": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Go" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переход" + } + } + } + }, + "package.kind.casks": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Casks" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Cask-пакеты" + } + } + } + }, + "package.kind.formulae": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Formulae" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Формулы" + } + } + } + }, + "package.pinned": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pinned" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закреплён" + } + } + } + }, + "palette.title.menu": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command Palette…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Палитра команд…" + } + } + } + }, + "security.brewVulnsInstalled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-vulns is installed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-vulns установлен" + } + } + } + }, + "security.brewVulnsMissing.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vulnerability scanning needs the official brew vulns subcommand." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Для проверки уязвимостей нужна официальная подкоманда brew vulns." + } + } + } + }, + "security.installBrewVulns": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install brew-vulns" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установить brew-vulns" + } + } + } + }, + "security.offlineSuppressed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode is on — vulnerability scanning is suppressed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим включён — проверка уязвимостей заблокирована." + } + } + } + }, + "security.scanInstalled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scan installed packages for known vulnerabilities" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверять установленные пакеты на известные уязвимости" + } + } + } + }, + "security.scanInstalled.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Opt-in, off by default. Runs the official brew vulns subcommand for installed formulae and stores findings locally." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Опционально, выключено по умолчанию. Запускает официальную подкоманду brew vulns для установленных формул и хранит результаты локально." + } + } + } + }, + "security.viewVulnerablePackages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "View vulnerable packages →" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать уязвимые пакеты →" + } + } + } + }, + "detail.security.cleanStale": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No advisories as of the last scan — re-scan to confirm" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "На момент последней проверки известных уязвимостей нет — проверьте ещё раз для подтверждения" + } + } + } + }, + "detail.security.lastScan.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Last scan: %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Последняя проверка: %@" + } + } + } + }, + "detail.security.notScanned.detail": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not scanned yet. Scanning checks every installed package — brew vulns can't scan just one." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверка ещё не выполнялась. Проверка проходит по всем установленным пакетам: brew vulns не умеет проверять только один пакет." + } + } + } + }, + "detail.security.openAdvisory.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open advisory: %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть бюллетень безопасности: %@" + } + } + } + }, + "detail.security.patchedIn.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Patched in %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Исправлено в %@" + } + } + } + }, + "detail.security.rescanHelp": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Runs brew vulns across your whole install — it can't scan a single package" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запускает brew vulns по всей установленной библиотеке; отдельный пакет проверить нельзя" + } + } + } + }, + "onboarding.aria": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew setup" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройка Homebrew" + } + } + } + }, + "onboarding.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Let's get Homebrew set up" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настроим Homebrew" + } + } + } + }, + "onboarding.body.mac": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser is a friendly face for Homebrew, the package manager for your Mac. Install it once and your library appears here automatically — no relaunch needed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-browser — удобный интерфейс для Homebrew, пакетного менеджера для Mac. Установите Homebrew один раз, и библиотека появится здесь автоматически — перезапуск не нужен." + } + } + } + }, + "onboarding.body.linux": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser is a friendly face for Homebrew, which runs on Linux too. Install it once and your library appears here automatically — no relaunch needed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-browser — удобный интерфейс для Homebrew, который работает и в Linux. Установите Homebrew один раз, и библиотека появится здесь автоматически — перезапуск не нужен." + } + } + } + }, + "onboarding.clt.before": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install the Xcode Command Line Tools first: run" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сначала установите Xcode Command Line Tools: выполните" + } + } + } + }, + "onboarding.clt.after": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "in Terminal and follow the prompts." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "в Терминале и следуйте подсказкам." + } + } + } + }, + "onboarding.linuxDeps.before": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install Homebrew's build dependencies first — on Debian/Ubuntu:" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сначала установите зависимости сборки Homebrew. Для Debian/Ubuntu:" + } + } + } + }, + "onboarding.linuxDeps.after": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "other distros: see the \"Homebrew on Linux\" page at docs.brew.sh." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "для других дистрибутивов см. страницу \"Homebrew on Linux\" на docs.brew.sh." + } + } + } + }, + "onboarding.installCopied": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install command copied to clipboard" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Команда установки скопирована" + } + } + } + }, + "onboarding.copyFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't copy command" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось скопировать команду" + } + } + } + }, + "onboarding.openTerminalFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't open Terminal" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось открыть Терминал" + } + } + } + }, + "onboarding.commandCopiedFallback": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The install command was copied instead — paste it into any terminal." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Команда установки скопирована — вставьте её в любой терминал." + } + } + } + }, + "onboarding.openTerminalOrCopyFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't open Terminal or copy the command" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось открыть Терминал или скопировать команду" + } + } + } + }, + "services.alreadyRunning": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Already running" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уже запущено" + } + } + } + }, + "services.empty.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install something like postgresql, redis, or nginx and services will show up here." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установите, например, postgresql, redis или nginx — службы появятся здесь." + } + } + } + }, + "services.empty.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No background services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Фоновых служб нет" + } + } + } + }, + "services.loadFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't load services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить службы" + } + } + } + }, + "services.loading": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading brew services…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем службы brew…" + } + } + } + }, + "services.notRunning": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not running" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не запущено" + } + } + } + }, + "services.restart": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart service" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезапустить службу" + } + } + } + }, + "services.start": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Start service" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запустить службу" + } + } + } + }, + "services.status.error": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "error" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "ошибка" + } + } + } + }, + "services.status.notLoaded": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "not loaded" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "не загружена" + } + } + } + }, + "services.status.running": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "running" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "запущена" + } + } + } + }, + "services.status.scheduled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "scheduled" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "запланирована" + } + } + } + }, + "services.status.stopped": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "stopped" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "остановлена" + } + } + } + }, + "services.status.unknown": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "unknown" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "неизвестно" + } + } + } + }, + "services.stop": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop service" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Остановить службу" + } + } + } + }, + "settings.appearance.aiFeatures": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "AI features" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Функции ИИ" + } + } + } + }, + "settings.appearance.aiFeatures.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "When off, only Homebrew's native metadata appears. AI-generated categories and descriptions are hidden." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Если выключено, отображаются только штатные метаданные Homebrew. ИИ-категории и описания скрываются." + } + } + } + }, + "settings.appearance.defaultLanding": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Default landing" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Стартовый раздел" + } + } + } + }, + "settings.brew.advanced": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Advanced" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Дополнительно" + } + } + } + }, + "settings.brew.analytics": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Analytics" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Аналитика" + } + } + } + }, + "settings.brew.analytics.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew sends anonymous install analytics to formulae.brew.sh by default. This changes Homebrew's own analytics preference." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Homebrew по умолчанию отправляет анонимную аналитику установок на formulae.brew.sh. Этот переключатель меняет собственную настройку аналитики Homebrew." + } + } + } + }, + "settings.brew.autoremove": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Autoremove" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить лишние зависимости" + } + } + } + }, + "settings.brew.autoremove.confirmMessage": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This runs brew autoremove to delete formulae installed only as dependencies and no longer needed by any package." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Будет выполнено brew autoremove: команда удалит формулы, установленные только как зависимости и больше не нужные ни одному пакету." + } + } + } + }, + "settings.brew.autoremove.confirmTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove unused dependencies?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить неиспользуемые зависимости?" + } + } + } + }, + "settings.brew.autoremove.failedToRun": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew autoremove failed to run" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось запустить brew autoremove" + } + } + } + }, + "settings.brew.autoremove.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Runs brew autoremove to remove formulae that were installed only as dependencies and are no longer needed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запускает brew autoremove, чтобы удалить формулы, установленные только как зависимости и больше не нужные ни одному пакету." + } + } + } + }, + "settings.brew.autoremoveUnused": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Autoremove unused dependencies" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить неиспользуемые зависимости" + } + } + } + }, + "settings.brew.confirmDestructive": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Confirm before uninstall / zap" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подтверждать удаление и zap-очистку" + } + } + } + }, + "settings.brew.confirmDestructive.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Ask for confirmation before irreversible actions: uninstalling a package, zap cleanup, or deleting a Brewfile." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перед необратимыми действиями (удаление пакета, zap-очистка, удаление Brewfile) приложение запрашивает подтверждение." + } + } + } + }, + "settings.brew.confirmations": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Confirmations" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подтверждения" + } + } + } + }, + "settings.brew.greedyUpgrades": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Greedy upgrades" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Расширенное обновление" + } + } + } + }, + "settings.brew.greedyUpgrades.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Include self-updating casks such as Chrome when running brew upgrade --greedy. Off by default." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Включает cask-пакеты с собственным механизмом обновления, например Chrome, при запуске brew upgrade --greedy. По умолчанию выключено." + } + } + } + }, + "settings.brew.sendAnalytics": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Send Homebrew install analytics" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отправлять аналитику установок Homebrew" + } + } + } + }, + "settings.fileUnreadable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings file is unreadable." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Файл настроек не читается." + } + } + } + }, + "settings.github.showStats": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show GitHub stats on package pages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывать статистику GitHub на страницах пакетов" + } + } + } + }, + "settings.github.showStats.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "When signed in, brew-browser checks whether you have starred packages with GitHub homepages." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "После входа brew-browser проверяет, какие пакеты с домашними страницами GitHub вы отметили звёздочкой." + } + } + } + }, + "settings.network.caskIconFetching": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cask icon fetching" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загрузка иконок cask-пакетов" + } + } + } + }, + "settings.network.catalogAutoRefresh": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Catalog auto-refresh" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Автообновление каталога" + } + } + } + }, + "settings.network.catalogStaleThreshold.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show stale catalog banner after %d days" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывать предупреждение об устаревшем каталоге через %d дней" + } + } + } + }, + "settings.network.offlineMode": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим" + } + } + } + }, + "settings.network.offlineMode.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Blocks all brew-browser network calls. brew commands you start still run normally." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Блокирует все сетевые запросы brew-browser. Команды brew, которые вы запускаете сами, продолжают работать как обычно." + } + } + } + }, + "settings.network.offlineMode.on": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode is on" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим включён" + } + } + } + }, + "settings.network.trendingTtl.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Trending cache TTL: %d minutes" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "TTL кэша трендов: %d мин." + } + } + } + }, + "settings.offlineToggleLocked": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode is on — this toggle is locked." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим включён — переключатель заблокирован." + } + } + } + }, + "settings.option.daily": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Daily" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ежедневно" + } + } + } + }, + "settings.option.installedOnly": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installed only" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Только установленные" + } + } + } + }, + "settings.option.off": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Off" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выкл." + } + } + } + }, + "settings.option.weekly": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Weekly" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Еженедельно" + } + } + } + }, + "settings.resetToDefaults": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reset to defaults" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сбросить настройки" + } + } + } + }, + "settings.section.security": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Security" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Безопасность" + } + } + } + }, + "settings.section.updates": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Updates" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновления" + } + } + } + }, + "table.actions": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Actions" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Действия" + } + } + } + }, + "table.name": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Name" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Имя" + } + } + } + }, + "table.sortBy": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sort by {label}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сортировать по столбцу «{label}»" + } + } + } + }, + "table.sortedAsc": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sorted by {label}, ascending. Click to reverse." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сортировка по столбцу «{label}» по возрастанию. Нажмите, чтобы изменить порядок." + } + } + } + }, + "table.sortedDesc": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sorted by {label}, descending. Click to reverse." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сортировка по столбцу «{label}» по убыванию. Нажмите, чтобы изменить порядок." + } + } + } + }, + "table.status": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Status" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Статус" + } + } + } + }, + "table.user": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "User" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пользователь" + } + } + } + }, + "dashboard.refresh.brewUpdateFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew update finished with errors" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew update завершился с ошибками" + } + } + } + }, + "dashboard.refresh.catalogFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Catalog refresh failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось обновить каталог" + } + } + } + }, + "dashboard.refresh.failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось обновить" + } + } + } + }, + "dashboard.refresh.successTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refreshed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновлено" + } + } + } + }, + "dashboard.refresh.successBody": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew taps + catalog + installed list all current" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "tap-репозитории brew, каталог и список установленных пакетов актуальны" + } + } + } + }, + "dashboard.doctor.failedToRun": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew doctor failed to run" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось запустить brew doctor" + } + } + } + }, + "dashboard.upgradeAll.success": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgraded {count} packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновлено пакетов: {count}" + } + } + } + }, + "storage.refreshDiskUsage": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh disk usage" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить сведения о месте на диске" + } + } + } + }, + "storage.diskProbeFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disk probe failed: {error}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось проверить диск: {error}" + } + } + } + }, + "storage.measuringDiskUsage": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Measuring disk usage… (du -sk on {prefix})" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Считаем место на диске… (du -sk для {prefix})" + } + } + } + }, + "storage.notPresent": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "not present" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "нет на диске" + } + } + } + }, + "storage.revealInFinder": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reveal {path} in Finder" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать {path} в Finder" + } + } + } + }, + "storage.showInFileManager": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show {path} in file manager" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать {path} в файловом менеджере" + } + } + } + }, + "storage.revealLabelInFinder": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reveal {label} in Finder" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать «{label}» в Finder" + } + } + } + }, + "storage.showLabelInFileManager": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show {label} in file manager" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать «{label}» в файловом менеджере" + } + } + } + }, + "storage.pathMissing": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Path doesn't exist" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Путь не существует" + } + } + } + }, + "resize.panel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Resize panel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Изменить размер панели" + } + } + } + }, + "resize.dragHint": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Drag to resize · double-click to reset" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Потяните, чтобы изменить размер; двойной щелчок сбрасывает ширину" + } + } + } + }, + "theme.cycle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cycle theme" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переключить тему" + } + } + } + }, + "theme.label": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Theme" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тема" + } + } + } + }, + "trending.fetchHistory": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Fetch trending history" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружать историю трендов" + } + } + } + }, + "trending.fetchHistory.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloads per-package trend history for sparklines and package detail charts." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружает историю установок по пакетам для мини-графиков и графиков в карточке пакета." + } + } + } + }, + "trending.sparklineAria": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install trend sparkline" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "График динамики установок" + } + } + } + }, + "trending.error.backend": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Backend not available: {error}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Бэкенд недоступен: {error}" + } + } + } + }, + "trending.error.failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Failed to load trending: {error}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить тренды: {error}" + } + } + } + }, + "trending.error.network": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't reach formulae.brew.sh" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось подключиться к formulae.brew.sh" + } + } + } + }, + "updates.autoCheck": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Auto-check daily" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверять ежедневно" + } + } + } + }, + "updates.autoCheck.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Automatically check for brew-browser updates once a day." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Автоматически проверять обновления brew-browser раз в день." + } + } + } + }, + "updates.available.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Click to open the updater." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нажмите, чтобы открыть обновление." + } + } + } + }, + "updates.channel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Channel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Канал" + } + } + } + }, + "updates.channel.label": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update channel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Канал обновлений" + } + } + } + }, + "updates.channel.stable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stable" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Стабильный" + } + } + } + }, + "updates.check.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Checks the Sparkle update feed for a newer brew-browser build." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверяет ленту Sparkle на наличие новой сборки brew-browser." + } + } + } + }, + "updates.checkForUpdates": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check for Updates…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить обновления…" + } + } + } + }, + "updates.checkNow": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check now" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить сейчас" + } + } + } + }, + "updates.lastChecked.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Last checked: %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Последняя проверка: %@" + } + } + } + }, + "updates.never": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Never" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Никогда" + } + } + } + }, + "updates.noBeta": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No beta channel in this release." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "В этом релизе нет бета-канала." + } + } + } + }, + "updates.offlineBlocked": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode blocks update checks." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим блокирует проверку обновлений." + } + } + } + }, + "upgrade.choosePackages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose packages to upgrade" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выберите пакеты для обновления" + } + } + } + }, + "upgrade.noOutdated": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No outdated packages." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устаревших пакетов нет." + } + } + } + }, + "upgrade.pinned.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pinned packages are not upgraded by brew. Unpin from the package detail page." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закреплённые пакеты не обновляются через brew. Снимите закрепление в карточке пакета." + } + } + } + }, + "deprecated": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "deprecated" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "устарело" + } + } + } + }, + "discover.activeFilters": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Active category filters" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Активные фильтры категорий" + } + } + } + }, + "discover.catalogFetched": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Fetched from formulae.brew.sh" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружено с formulae.brew.sh" + } + } + } + }, + "discover.catalogRefreshed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Catalog refreshed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Каталог обновлён" + } + } + } + }, + "discover.categoryBrowsingHidden": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Category browsing is hidden because AI features are turned off in Settings → Appearance." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обзор по категориям скрыт, потому что функции ИИ выключены в «Настройки → Оформление»." + } + } + } + }, + "discover.categoryTileAria": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "{category} - {count} packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "{category} — {count} пакетов" + } + } + } + }, + "discover.deprecatedDefault": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deprecated - may be removed in a future Homebrew update." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устарело — может быть удалено при одном из будущих обновлений Homebrew." + } + } + } + }, + "discover.deprecatedReason": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deprecated: {reason}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устарело: {reason}" + } + } + } + }, + "discover.disabledDefault": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disabled - no longer available via Homebrew." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отключено — больше недоступно через Homebrew." + } + } + } + }, + "discover.disabledReason": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disabled: {reason}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отключено: {reason}" + } + } + } + }, + "discover.emptyFilter": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No packages match this filter." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Под этот фильтр пакеты не подходят." + } + } + } + }, + "discover.groupPackagesLabel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Packages in {group}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пакеты в группе «{group}»" + } + } + } + }, + "discover.loadingCategories": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading categories…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем категории…" + } + } + } + }, + "discover.noResults": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing matches \"{query}\"." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "По запросу «{query}» ничего не найдено." + } + } + } + }, + "discover.noResults.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Try a shorter or different term." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Попробуйте более короткий или другой запрос." + } + } + } + }, + "discover.noResultsInCategories": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No \"{query}\" results in the selected categories." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "По запросу «{query}» нет результатов в выбранных категориях." + } + } + } + }, + "discover.noResultsInCategories.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Try removing a chip or broadening the search term." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уберите фильтр категории или расширьте запрос." + } + } + } + }, + "discover.packagesInCategory": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Packages in {category}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пакеты в категории «{category}»" + } + } + } + }, + "discover.removeFilter": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove {category} filter" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Убрать фильтр «{category}»" + } + } + } + }, + "discover.searchAria": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search Homebrew" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск по Homebrew" + } + } + } + }, + "discover.searchFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск не удался" + } + } + } + }, + "discover.searchPlaceholder": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search the Homebrew index…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск в индексе Homebrew…" + } + } + } + }, + "discover.searchPromptTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Type a query to search the Homebrew index" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Введите запрос для поиска в индексе Homebrew" + } + } + } + }, + "discover.staleDismissAria": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dismiss catalog staleness banner" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скрыть предупреждение об устаревшем каталоге" + } + } + } + }, + "discover.subgroupNote": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "grouped by overlapping category" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "сгруппировано по пересекающимся категориям" + } + } + } + }, + "discover.subgroupsBody": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew has no official sub-category data, so this is a co-occurrence grouping: within \"{category}\", packages are bucketed by the other categories they also belong to. A package with several overlapping categories appears in each bucket. \"General {category}\" holds packages tagged only \"{category}\" - it's often the largest group, and this is a grouping aid, not a strict taxonomy." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "У Homebrew нет официальных подкатегорий, поэтому это группировка по совместной встречаемости: внутри «{category}» пакеты раскладываются по другим категориям, к которым они тоже относятся. Пакет с несколькими пересекающимися категориями попадает в каждую такую группу. «Общее: {category}» содержит пакеты, отмеченные только категорией «{category}». Это подсказка для навигации, а не строгая таксономия." + } + } + } + }, + "discover.subgroupsLabel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About sub-categories" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О подкатегориях" + } + } + } + }, + "discover.subgroupsTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "How these sub-groups are built" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Как устроены эти подгруппы" + } + } + } + }, + "package.installTrendFor": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "{name} install trend" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Динамика установок {name}" + } + } + } + }, + "package.openToken": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open {token}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть {token}" + } + } + } + }, + "recovery.reason.alreadyInstalled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "{name} is already installed outside Homebrew." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "{name} уже установлен вне Homebrew." + } + } + } + }, + "recovery.reason.stillRequired": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "{name} is still required by another installed package." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "{name} ещё нужен другому установленному пакету." + } + } + } + }, + "vulnerabilities.scanFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vulnerability scan failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось выполнить проверку уязвимостей" + } + } + } + }, + "vulnerabilities.scanOneFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vulnerability scan failed for {name}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось проверить уязвимости для {name}" + } + } + } + }, + "settings.github.corruptSettings": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings file unreadable - visit the Network section to reset." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Файл настроек не читается — откройте раздел «Сеть», чтобы сбросить настройки." + } + } + } + }, + "settings.github.githubUserFallback": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "github user" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "пользователь GitHub" + } + } + } + }, + "settings.github.privacy.afterStruct": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " view crosses the IPC boundary." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "." + } + } + } + }, + "settings.github.privacy.beforeStruct": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser stores your token in {keyringName}. The token is never sent over IPC to the renderer, never written to disk, and never logged. Only the derived " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-browser хранит токен в {keyringName}. Токен никогда не отправляется в renderer-процесс по IPC, никогда не записывается на диск и никогда не попадает в логи. Через IPC передаётся только производное представление " + } + } + } + }, + "settings.github.scopes.afterNotifications": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " to enable watching. No private-repo access, no email read, no admin." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " для отслеживания. Доступа к приватным репозиториям, email и администрированию нет." + } + } + } + }, + "settings.github.scopes.afterPublicRepo": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " to enable starring and issue filing, and " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " для звёзд и создания issue, а также " + } + } + } + }, + "settings.github.scopes.afterReadUser": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " to display your username, " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " для отображения имени пользователя, " + } + } + } + }, + "settings.github.scopes.beforeReadUser": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign-in is optional. The minimum scopes requested are " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вход необязателен. Запрашиваются минимальные разрешения: " + } + } + } + }, + "settings.github.signInHint": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Opens GitHub's standard \"Authorize app\" flow in your browser. No password is ever entered into brew-browser. This is required to Star, Watch, and file issues on GitHub with brew-browser." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открывает стандартный поток GitHub «Authorize app» в браузере. Пароль никогда не вводится в brew-browser. Вход нужен, чтобы ставить звёзды, отслеживать репозитории и создавать issue в GitHub из brew-browser." + } + } + } + }, + "settings.github.signInPurpose": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "What sign-in is used for" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Для чего используется вход" + } + } + } + }, + "settings.github.signedInAs": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Signed in as" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выполнен вход как" + } + } + } + }, + "settings.github.statsHint.afterHost": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "." + } + } + } + }, + "settings.github.statsHint.beforeHost": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show repo stars, forks, and last release for any package whose homepage is a GitHub URL. brew-browser fetches public repo metadata from " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывает звёзды, форки и последний релиз для пакетов, у которых домашняя страница ведёт на GitHub. brew-browser загружает публичные метаданные репозитория из " + } + } + } + }, + "settings.github.scopesLabel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scopes" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разрешения" + } + } + } + } + }, + "version": "1.0" +} diff --git a/native/Sources/BrewBrowser/Resources/en.lproj/Localizable.strings b/native/Sources/BrewBrowser/Resources/en.lproj/Localizable.strings new file mode 100644 index 0000000..0696150 --- /dev/null +++ b/native/Sources/BrewBrowser/Resources/en.lproj/Localizable.strings @@ -0,0 +1,830 @@ +"AI & ML" = "AI & ML"; +"Browsers" = "Browsers"; +"Cloud & DevOps" = "Cloud & DevOps"; +"Communication" = "Communication"; +"Data" = "Data"; +"Developer Tools" = "Developer Tools"; +"Editors & IDEs" = "Editors & IDEs"; +"Education" = "Education"; +"Games & Entertainment" = "Games & Entertainment"; +"Graphics & Design" = "Graphics & Design"; +"Music" = "Music"; +"Office & Docs" = "Office & Docs"; +"Productivity" = "Productivity"; +"System Utilities" = "System Utilities"; +"Video & Audio" = "Video & Audio"; +"Writing" = "Writing"; +"Uncategorized" = "Uncategorized"; +"app.controls" = "App controls"; +"chrome.github.connectionStatus" = "GitHub connection status"; +"chrome.donate.aria" = "Donate to brew-browser on GitHub Sponsors"; +"chrome.donate.title" = "Donate via GitHub Sponsors"; +"command.openActivity" = "Open Activity"; +"command.openDiscover" = "Open Discover"; +"command.openLibrary" = "Open Library"; +"command.openSnapshots" = "Open Snapshots"; +"command.openTrending" = "Open Trending"; +"command.refreshLibrary" = "Refresh Library"; +"command.refreshSnapshots" = "Refresh Snapshots"; +"command.refreshTrending" = "Refresh Trending"; +"command.toggleActivityDrawer" = "Toggle Activity drawer"; +"github.connectedAs" = "GitHub: connected as @{username}"; +"github.scopeIncomplete" = "GitHub: signed in, but scope incomplete — click to fix"; +"keyboard.close" = "close"; +"keyboard.navigate" = "navigate"; +"keyboard.open" = "open"; +"language.english" = "English"; +"language.hint.reload" = "Changing the language reloads brew-browser."; +"language.label" = "Language"; +"language.russian" = "Russian"; +"language.system" = "System language"; +"nav.activity" = "Activity"; +"nav.dashboard" = "Dashboard"; +"nav.discover" = "Discover"; +"nav.library" = "Library"; +"nav.primary" = "Primary navigation"; +"nav.services" = "Services"; +"nav.snapshots" = "Snapshots"; +"nav.trending" = "Trending"; +"nav.showSidebar" = "Show sidebar"; +"nav.hideSidebar" = "Hide sidebar"; +"package.installed" = "installed"; +"package.kind.cask" = "cask"; +"package.kind.formula" = "formula"; +"palette.empty" = "No results."; +"palette.group.actions" = "Actions"; +"palette.group.commands" = "Commands"; +"palette.group.index" = "Index"; +"palette.group.installed" = "Installed"; +"palette.results" = "Command palette results"; +"palette.search.placeholder" = "Type a command, package, or section."; +"palette.searchLabel" = "Command palette search"; +"palette.title" = "Command palette"; +"palette.group.view" = "View"; +"search.allResultsInDiscover" = "See all results in Discover →"; +"search.clear" = "Clear"; +"search.clearSearch" = "Clear search"; +"search.noMatches" = "No matches for \"{query}\"."; +"search.packages" = "Search packages"; +"search.packagesPlaceholder" = "Search packages…"; +"search.results" = "Search results"; +"search.searching" = "Searching…"; +"settings.open" = "Open Settings"; +"settings.close" = "Close Settings"; +"settings.paneLabel" = "Settings — {section}"; +"settings.sections" = "Settings sections"; +"settings.section.about" = "About"; +"settings.section.activity" = "Activity"; +"settings.section.appearance" = "Appearance"; +"settings.section.brew" = "Brew"; +"settings.section.github" = "GitHub"; +"settings.section.network" = "Network"; +"settings.shortcutTitle" = "Settings (⌘,)"; +"settings.title" = "Settings"; +"theme.change" = "Change theme"; +"theme.dark" = "Dark"; +"theme.light" = "Light"; +"theme.title" = "Theme: {theme}"; +"theme.system" = "System"; +"trending.window.30d" = "30d"; +"trending.window.90d" = "90d"; +"trending.window.365d" = "365d"; +"updates.available" = "Update available"; +"updates.availableLabel" = "Update available: brew-browser {version}. Click to open Settings."; +"updates.dismissVersion" = "Dismiss update notification for v{version}"; +"updates.installing" = "Installing update"; +"updates.skipVersion" = "Skip this version"; +"updates.versionAvailable" = "brew-browser {version} is available"; +"vulnerability.packages" = "{count} vulnerable packages"; +"About" = "About"; +"About Brew Browser" = "About Brew Browser"; +"Activity" = "Activity"; +"Actions" = "Actions"; +"Advanced" = "Advanced"; +"AI features" = "AI features"; +"All" = "All"; +"All Categories" = "All Categories"; +"All current" = "All current"; +"Analytics" = "Analytics"; +"Appearance" = "Appearance"; +"App version" = "App version"; +"Autoremove" = "Autoremove"; +"Autoremove unused dependencies" = "Autoremove unused dependencies"; +"Auto-check daily" = "Auto-check daily"; +"Body (Markdown)" = "Body (Markdown)"; +"Brew" = "Brew"; +"Browse" = "Browse"; +"Cancel" = "Cancel"; +"Casks" = "Casks"; +"Casks (Caskroom)" = "Casks (Caskroom)"; +"Caveats" = "Caveats"; +"Check for updates now" = "Check for updates now"; +"Check now" = "Check now"; +"Choose packages to upgrade" = "Choose packages to upgrade"; +"Choose…" = "Choose…"; +"Categories unavailable" = "Categories unavailable"; +"Categories" = "Categories"; +"Clear" = "Clear"; +"Clear completed" = "Clear completed"; +"Clear Finished" = "Clear Finished"; +"Clear categories" = "Clear categories"; +"Clear category filter" = "Clear category filter"; +"Clear filter" = "Clear filter"; +"Click the code to copy it." = "Click the code to copy it."; +"Close" = "Close"; +"Command Palette…" = "Command Palette…"; +"Composition" = "Composition"; +"Confirm before uninstall / zap" = "Confirm before uninstall / zap"; +"Confirmations" = "Confirmations"; +"Contacting GitHub…" = "Contacting GitHub…"; +"Create" = "Create"; +"Dashboard" = "Dashboard"; +"Dark" = "Dark"; +"Default landing" = "Default landing"; +"Delete" = "Delete"; +"Dependency" = "Dependency"; +"Description" = "Description"; +"Download cache" = "Download cache"; +"Deselect all" = "Deselect all"; +"Discover" = "Discover"; +"Dismiss" = "Dismiss"; +"Dismiss for this session" = "Dismiss for this session"; +"Donate to the project" = "Donate to the project"; +"Enter this code:" = "Enter this code:"; +"Esc" = "Esc"; +"Export…" = "Export…"; +"Exposure" = "Exposure"; +"Fetch latest categories & descriptions" = "Fetch latest categories & descriptions"; +"Fetch trending history" = "Fetch trending history"; +"File issue" = "File issue"; +"File an issue" = "File an issue"; +"Filter" = "Filter"; +"Formulae" = "Formulae"; +"Formulae (Cellar)" = "Formulae (Cellar)"; +"GitHub" = "GitHub"; +"Greedy upgrades (include self-updating casks)" = "Greedy upgrades (include self-updating casks)"; +"Homebrew" = "Homebrew"; +"Icon source" = "Icon source"; +"Import…" = "Import…"; +"Install" = "Install"; +"Install brew-vulns" = "Install brew-vulns"; +"Install complete. Relaunch to use the new version." = "Install complete. Relaunch to use the new version."; +"Install trend" = "Install trend"; +"Installed" = "Installed"; +"Installed only" = "Installed only"; +"Installing…" = "Installing…"; +"instead" = "instead"; +"Keep last N completed jobs" = "Keep last N completed jobs"; +"Language" = "Language"; +"Labels" = "Labels"; +"Latest" = "Latest"; +"Library" = "Library"; +"License" = "License"; +"License mismatch" = "License mismatch"; +"Light" = "Light"; +"Lines per job" = "Lines per job"; +"Loading GitHub stats…" = "Loading GitHub stats…"; +"Loading settings…" = "Loading settings…"; +"Loading sign-in status…" = "Loading sign-in status…"; +"Logs (var/log)" = "Logs (var/log)"; +"Manual" = "Manual"; +"Name" = "Name"; +"Network" = "Network"; +"New Snapshot" = "New Snapshot"; +"No beta channel in this release." = "No beta channel in this release."; +"No background services" = "No background services"; +"No known vulnerabilities." = "No known vulnerabilities."; +"No known vulnerabilities" = "No known vulnerabilities"; +"No outdated packages." = "No outdated packages."; +"No results." = "No results."; +"No snapshots yet" = "No snapshots yet"; +"Not enough data yet" = "Not enough data yet"; +"Not scanned yet." = "Not scanned yet."; +"Offline Mode" = "Offline Mode"; +"Off" = "Off"; +"OK" = "OK"; +"Open in browser" = "Open in browser"; +"Open in Finder" = "Open in Finder"; +"Outbound paths (read-only)" = "Outbound paths (read-only)"; +"Package detail" = "Package detail"; +"Patched in" = "Patched in"; +"Pinned" = "Pinned"; +"Pinned packages are not upgraded by brew. Unpin from the package detail page." = "Pinned packages are not upgraded by brew. Unpin from the package detail page."; +"Recent" = "Recent"; +"RECENT" = "RECENT"; +"Refresh" = "Refresh"; +"Refresh from brew.sh →" = "Refresh from brew.sh →"; +"Refreshing…" = "Refreshing…"; +"Remove" = "Remove"; +"Repository" = "Repository"; +"Reset to defaults" = "Reset to defaults"; +"Resize package detail panel" = "Resize package detail panel"; +"Restore" = "Restore"; +"Retry" = "Retry"; +"Re-scan all" = "Re-scan all"; +"Scan all packages" = "Scan all packages"; +"Scan installed packages for known vulnerabilities" = "Scan installed packages for known vulnerabilities"; +"Scan now" = "Scan now"; +"Scanning all…" = "Scanning all…"; +"Scanning…" = "Scanning…"; +"Scrub — also remove the latest versions' cached downloads (more aggressive)" = "Scrub — also remove the latest versions' cached downloads (more aggressive)"; +"Search" = "Search"; +"Search packages…" = "Search packages…"; +"Searching…" = "Searching…"; +"Security" = "Security"; +"Select all" = "Select all"; +"Send Homebrew install analytics" = "Send Homebrew install analytics"; +"Service" = "Service"; +"Services" = "Services"; +"Settings" = "Settings"; +"Settings file is unreadable." = "Settings file is unreadable."; +"Show GitHub stats on package pages" = "Show GitHub stats on package pages"; +"Show sidebar" = "Show sidebar"; +"Hide sidebar" = "Hide sidebar"; +"Sign in" = "Sign in"; +"Sign in to GitHub" = "Sign in to GitHub"; +"Sign in with GitHub" = "Sign in with GitHub"; +"Sign out" = "Sign out"; +"Sign-in denied." = "Sign-in denied."; +"Similar packages" = "Similar packages"; +"Size" = "Size"; +"Snapshot" = "Snapshot"; +"Snapshots" = "Snapshots"; +"Stable" = "Stable"; +"Start" = "Start"; +"Stop" = "Stop"; +"Storage" = "Storage"; +"Status" = "Status"; +"Submit" = "Submit"; +"Support brew-browser via GitHub Sponsors" = "Support brew-browser via GitHub Sponsors"; +"System" = "System"; +"System language" = "System language"; +"Tags" = "Tags"; +"Tap" = "Tap"; +"Terminal" = "Terminal"; +"Theme" = "Theme"; +"Title" = "Title"; +"Token" = "Token"; +"Top categories in your library" = "Top categories in your library"; +"Trending" = "Trending"; +"Trending cache TTL" = "Trending cache TTL"; +"Try again" = "Try again"; +"Type" = "Type"; +"Type a command, package, or section." = "Type a command, package, or section."; +"Update available" = "Update available"; +"Update channel" = "Update channel"; +"Updates" = "Updates"; +"Updates available" = "Updates available"; +"Vulnerable" = "Vulnerable"; +"Velocity" = "Velocity"; +"Upgrade" = "Upgrade"; +"Upgrade all" = "Upgrade all"; +"Upgrade available" = "Upgrade available"; +"Upgrade to fix" = "Upgrade to fix"; +"Version" = "Version"; +"Verbose — list every file removed" = "Verbose — list every file removed"; +"View vulnerable packages →" = "View vulnerable packages →"; +"Waiting for authorization…" = "Waiting for authorization…"; +"Watch" = "Watch"; +"Watching" = "Watching"; +"Why install this?" = "Why install this?"; +"Window" = "Window"; +"Window vibrancy" = "Window vibrancy"; +"— all enrichment is baked into the app binary." = "— all enrichment is baked into the app binary."; +"Zero LLM calls are made from your machine" = "Zero LLM calls are made from your machine"; +"Zero telemetry. Zero accounts." = "Zero telemetry. Zero accounts."; +"built with" = "built with"; +"close" = "close"; +"close Esc" = "close Esc"; +"dependency" = "dependency"; +"formula" = "formula"; +"cask" = "cask"; +"installed" = "installed"; +"not present" = "not present"; +"pinned" = "pinned"; +"source" = "source"; +"unavailable" = "unavailable"; +"working…" = "working…"; +"Adopt existing" = "Adopt existing"; +"Backend not available" = "Backend not available"; +"Catalog refresh failed" = "Catalog refresh failed"; +"Change theme" = "Change theme"; +"Changing the language reloads brew-browser." = "Changing the language reloads brew-browser."; +"Cleanup failed" = "Cleanup failed"; +"Clean up cache…" = "Clean up cache…"; +"Clean up" = "Clean up"; +"Cleaning…" = "Cleaning…"; +"Cleaning up Homebrew cache" = "Cleaning up Homebrew cache"; +"Command palette" = "Command palette"; +"Command palette search" = "Command palette search"; +"Command palette results" = "Command palette results"; +"Couldn't change brew analytics" = "Couldn't change brew analytics"; +"Couldn't load services" = "Couldn't load services"; +"Couldn't load snapshots" = "Couldn't load snapshots"; +"Couldn't load packages." = "Couldn't load packages."; +"Couldn't open in file manager" = "Couldn't open in file manager"; +"Couldn't reveal in Finder" = "Couldn't reveal in Finder"; +"Disk probe failed" = "Disk probe failed"; +"Donate via GitHub Sponsors" = "Donate via GitHub Sponsors"; +"Donate to brew-browser on GitHub Sponsors" = "Donate to brew-browser on GitHub Sponsors"; +"Everything is up to date" = "Everything is up to date"; +"English" = "English"; +"Follows the macOS theme when set to System." = "Follows the macOS theme when set to System."; +"Failed to load packages" = "Failed to load packages"; +"Force remove" = "Force remove"; +"GitHub connection status" = "GitHub connection status"; +"Homebrew analytics disabled" = "Homebrew analytics disabled"; +"Homebrew analytics enabled" = "Homebrew analytics enabled"; +"Homebrew was not found on this Mac." = "Homebrew was not found on this Mac."; +"Index" = "Index"; +"Installed as a dependency, not requested directly" = "Installed as a dependency, not requested directly"; +"Invalid URL" = "Invalid URL"; +"Loading your packages…" = "Loading your packages…"; +"Measuring disk usage…" = "Measuring disk usage…"; +"Nothing's run yet." = "Nothing's run yet."; +"Open Activity" = "Open Activity"; +"Open Discover" = "Open Discover"; +"Open Library" = "Open Library"; +"Open Settings" = "Open Settings"; +"Open Snapshots" = "Open Snapshots"; +"Open Trending" = "Open Trending"; +"Open Library, filter outdated" = "Open Library, filter outdated"; +"Other" = "Other"; +"Pick which packages to upgrade" = "Pick which packages to upgrade"; +"Refresh Library" = "Refresh Library"; +"Refresh Snapshots" = "Refresh Snapshots"; +"Refresh Trending" = "Refresh Trending"; +"Refresh disk usage" = "Refresh disk usage"; +"Refreshed" = "Refreshed"; +"Report an issue on GitHub" = "Report an issue on GitHub"; +"Report to brew-browser" = "Report to brew-browser"; +"Remove from history" = "Remove from history"; +"Remove it anyway, ignoring packages that depend on it." = "Remove it anyway, ignoring packages that depend on it."; +"Remove unused dependencies?" = "Remove unused dependencies?"; +"Removing unused dependencies" = "Removing unused dependencies"; +"Removing…" = "Removing…"; +"Replace the existing copy with a fresh Homebrew install." = "Replace the existing copy with a fresh Homebrew install."; +"Run brew doctor" = "Run brew doctor"; +"Run brew doctor and stream the diagnostics into Activity" = "Run brew doctor and stream the diagnostics into Activity"; +"Run brew update, refresh the catalog from formulae.brew.sh, and reload your installed list" = "Run brew update, refresh the catalog from formulae.brew.sh, and reload your installed list"; +"Russian" = "Russian"; +"Search packages" = "Search packages"; +"Search results" = "Search results"; +"See the Activity drawer." = "See the Activity drawer."; +"Settings (⌘,)" = "Settings (⌘,)"; +"Settings sections" = "Settings sections"; +"Submit issue" = "Submit issue"; +"This will install packages from the snapshot. Existing packages are skipped." = "This will install packages from the snapshot. Existing packages are skipped."; +"Toggle Activity drawer" = "Toggle Activity drawer"; +"Upgrade-all failed" = "Upgrade-all failed"; +"Upgrading…" = "Upgrading…"; +"When off, only Homebrew's native metadata appears. Categories (sidebar tile grid, donut chart, chip filters) are also hidden because they're AI-generated." = "When off, only Homebrew's native metadata appears. Categories (sidebar tile grid, donut chart, chip filters) are also hidden because they're AI-generated."; +"When on, brew-browser shows extra metadata generated at build time by AI: friendly names, expanded descriptions, use cases, similar package suggestions, and category tags." = "When on, brew-browser shows extra metadata generated at build time by AI: friendly names, expanded descriptions, use cases, similar package suggestions, and category tags."; +"Which section opens when you launch brew-browser." = "Which section opens when you launch brew-browser."; +"Requires app restart to take effect. The default (HudWindow) matches the rest of macOS." = "Requires app restart to take effect. The default (HudWindow) matches the rest of macOS."; +"all packages" = "all packages"; +"brew commands kicked off from here will show up in this list." = "brew commands kicked off from here will show up in this list."; +"disabled" = "disabled"; +"Advisory" = "Advisory"; +"Catalog unavailable" = "Catalog unavailable"; +"Check this package for known vulnerabilities." = "Check this package for known vulnerabilities."; +"Commands" = "Commands"; +"Couldn't load packages" = "Couldn't load packages"; +"Couldn't load trending" = "Couldn't load trending"; +"Copy" = "Copy"; +"Copy the command to the clipboard" = "Copy the command to the clipboard"; +"Deprecated" = "Deprecated"; +"Disabled" = "Disabled"; +"Disabled by Offline Mode" = "Disabled by Offline Mode"; +"Blocked by Offline Mode. Disable in Settings → Network." = "Blocked by Offline Mode. Disable in Settings → Network."; +"Fetching install counts from formulae.brew.sh…" = "Fetching install counts from formulae.brew.sh…"; +"Homebrew isn't installed" = "Homebrew isn't installed"; +"Install Homebrew" = "Install Homebrew"; +"Install `brew vulns` to scan this package for known vulnerabilities." = "Install `brew vulns` to scan this package for known vulnerabilities."; +"Install the Xcode Command Line Tools" = "Install the Xcode Command Line Tools"; +"Installs" = "Installs"; +"Installs, upgrades, uninstalls, and updates show up here." = "Installs, upgrades, uninstalls, and updates show up here."; +"Loading snapshots…" = "Loading snapshots…"; +"Loading the Homebrew catalog…" = "Loading the Homebrew catalog…"; +"Members also categorized here" = "Members also categorized here"; +"Members of this category with no other category" = "Members of this category with no other category"; +"No activity yet" = "No activity yet"; +"No packages" = "No packages"; +"No results" = "No results"; +"No summary available — view details" = "No summary available — view details"; +"Not installed" = "Not installed"; +"Offline Mode is on — trending needs formulae.brew.sh." = "Offline Mode is on — trending needs formulae.brew.sh."; +"Open Dashboard" = "Open Dashboard"; +"Open Services" = "Open Services"; +"Open Terminal" = "Open Terminal"; +"Outdated" = "Outdated"; +"Paste the official install command (from brew.sh) into Terminal:" = "Paste the official install command (from brew.sh) into Terminal:"; +"Reading your Homebrew install…" = "Reading your Homebrew install…"; +"Restart" = "Restart"; +"Runs brew vulns across your whole install — it can't scan a single package" = "Runs brew vulns across your whole install — it can't scan a single package"; +"Save your current setup so you can restore it on another Mac. Snapshots live in ~/Library/Application Support/brew-browser/brewfiles/ — findable outside the app too." = "Save your current setup so you can restore it on another Mac. Snapshots live in ~/Library/Application Support/brew-browser/brewfiles/ — findable outside the app too."; +"Scanning all packages…" = "Scanning all packages…"; +"Sign in to GitHub happens on first action." = "Sign in to GitHub happens on first action."; +"Sign in to GitHub to star this repository" = "Sign in to GitHub to star this repository"; +"Sign in to GitHub to watch this repository" = "Sign in to GitHub to watch this repository"; +"Snapshot error" = "Snapshot error"; +"Star" = "Star"; +"Star this repository" = "Star this repository"; +"Starred" = "Starred"; +"Stop watching" = "Stop watching"; +"Stored in ~/Library/Application Support/brew-browser/brewfiles/" = "Stored in ~/Library/Application Support/brew-browser/brewfiles/"; +"The Brewfile will be removed from disk. This cannot be undone." = "The Brewfile will be removed from disk. This cannot be undone."; +"The Homebrew installer needs them. Run this in Terminal and follow the prompt:" = "The Homebrew installer needs them. Run this in Terminal and follow the prompt:"; +"The bundled package catalog couldn't be loaded." = "The bundled package catalog couldn't be loaded."; +"The brew-vulns subcommand isn't installed." = "The brew-vulns subcommand isn't installed."; +"settings.vulnerabilities.cleanResult" = "No known vulnerabilities across your installed packages."; +"settings.vulnerabilities.helperInstalledTitle" = "brew-vulns installed"; +"settings.vulnerabilities.initialScanBody" = "Running an initial scan now…"; +"settings.vulnerabilities.installFailedTitle" = "Couldn't install brew-vulns"; +"settings.vulnerabilities.installPrompt" = "Vulnerability scanning needs the official brew vulns subcommand. Install it now? This runs brew install homebrew/brew-vulns/brew-vulns."; +"settings.vulnerabilities.lastScan" = "Last scan"; +"settings.vulnerabilities.offlineHint" = "Offline Mode is on — vulnerability scanning is suppressed even if this toggle is on. Disable Offline Mode above to enable scanning."; +"settings.vulnerabilities.privacyHint" = "Opt-in, off by default. When on, brew-browser shells out to the official brew vulns subcommand (Homebrew project), which queries OSV.dev (operated by Google) for known vulnerabilities affecting your installed formulae. If you're also signed in to GitHub, individual GHSA-IDs are enriched with details from api.github.com. Findings are cached locally; no package list leaves your machine except the queries brew vulns itself makes to OSV."; +"settings.vulnerabilities.scanAllTitle" = "Run brew vulns against every installed formula"; +"This installs packages from the snapshot. Existing packages are skipped." = "This installs packages from the snapshot. Existing packages are skipped."; +"Use" = "Use"; +"User" = "User"; +"Uninstall" = "Uninstall"; +"Unstar this repository" = "Unstar this repository"; +"Vulnerability Scanning" = "Vulnerability Scanning"; +"Waiting for Homebrew… the app continues automatically once it's installed." = "Waiting for Homebrew… the app continues automatically once it's installed."; +"Watch for activity" = "Watch for activity"; +"Working… (see Activity)" = "Working… (see Activity)"; +"Your browser opened to GitHub's device page. Enter this code (it's copied to your clipboard):" = "Your browser opened to GitHub's device page. Enter this code (it's copied to your clipboard):"; +"brew-browser hit an unexpected error running this command." = "brew-browser hit an unexpected error running this command."; +"brew-browser is a window onto Homebrew — install it and the app takes it from there." = "brew-browser is a window onto Homebrew — install it and the app takes it from there."; +"formulae.brew.sh returned no entries." = "formulae.brew.sh returned no entries."; +"instead." = "instead."; +"Already running" = "Already running"; +"App Store Version Detected" = "App Store Version Detected"; +"Cancel sign in" = "Cancel sign in"; +"Click to copy" = "Click to copy"; +"Code copied to clipboard" = "Code copied to clipboard"; +"Code expired. Please try again." = "Code expired. Please try again."; +"Couldn't copy code" = "Couldn't copy code"; +"Delete failed" = "Delete failed"; +"Deleted snapshot" = "Deleted snapshot"; +"Export failed" = "Export failed"; +"Exported snapshot" = "Exported snapshot"; +"Import failed" = "Import failed"; +"Imported snapshot" = "Imported snapshot"; +"Install & Override" = "Install & Override"; +"Installation Blocked" = "Installation Blocked"; +"Loading brew services…" = "Loading brew services…"; +"No background services." = "No background services."; +"Install something like postgresql, redis, or nginx and they'll show up here." = "Install something like postgresql, redis, or nginx and they'll show up here."; +"Refresh services" = "Refresh services"; +"Restore complete" = "Restore complete"; +"Restore failed" = "Restore failed"; +"Snapshot failed" = "Snapshot failed"; +"Snapshot saved" = "Snapshot saved"; +"Start service" = "Start service"; +"Stop service" = "Stop service"; +"Restart service" = "Restart service"; +"Not running" = "Not running"; +"Brew services" = "Brew services"; +"Open package details" = "Open package details"; +"Save your current setup so you can restore it on another machine. Snapshots live in ~/.local/share/brew-browser/brewfiles/ — findable outside the app too." = "Save your current setup so you can restore it on another machine. Snapshots live in ~/.local/share/brew-browser/brewfiles/ — findable outside the app too."; +"This will remove the package from your system." = "This will remove the package from your system."; +"Overwrite manual installation?" = "Overwrite manual installation?"; +"An existing version was found in your Applications folder. If you proceed, Homebrew will force-install and overwrite the existing bundle." = "An existing version was found in your Applications folder. If you proceed, Homebrew will force-install and overwrite the existing bundle."; +"An existing App Store version was found in your Applications folder." = "An existing App Store version was found in your Applications folder."; +"App Store bundles are system-protected and cannot be overwritten by Homebrew." = "App Store bundles are system-protected and cannot be overwritten by Homebrew."; +"Resolution: delete the App Store version from /Applications, then install it again via Homebrew." = "Resolution: delete the App Store version from /Applications, then install it again via Homebrew."; +"Use the Sign in with GitHub button in Settings." = "Use the Sign in with GitHub button in Settings."; +"Re-authorize" = "Re-authorize"; +"Upgrade failed" = "Upgrade failed"; +"Upgraded packages" = "Upgraded packages"; +"Watching package" = "Watching package"; +"Stopped watching package" = "Stopped watching package"; +"Starred package" = "Starred package"; +"Unstarred package" = "Unstarred package"; +"Signed in as GitHub user." = "Signed in as GitHub user."; +"settings.activity.rangeDefault" = "Range {min}-{max}. Default {default}."; +"settings.activity.retentionNote" = "These limits apply to future job persistence. Existing retained data is not trimmed retroactively."; +"settings.brew.analyticsDesc.beforeCommand" = "Homebrew sends anonymous install analytics to formulae.brew.sh by default. This toggle flips Homebrew's setting (the same as running "; +"settings.brew.analyticsDesc.afterCommand" = " in your terminal)."; +"settings.brew.analyticsToggle" = "Send Homebrew install analytics"; +"settings.brew.confirmationsDesc" = "Destructive actions (Uninstall, Zap, Delete Brewfile) ask before proceeding. Power users can turn this off once they're sure."; +"settings.brew.advancedDesc.beforeCommand" = "Extra "; +"settings.brew.advancedDesc.afterCommand" = " options for power users."; +"settings.brew.greedyDesc.beforeFlag" = "Adds "; +"settings.brew.greedyDesc.between" = " to "; +"settings.brew.greedyDesc.afterCommand" = " so casks that update themselves (like Chrome) are upgraded too. Off by default - greedy can churn apps that manage their own updates."; +"settings.brew.autoremoveDesc.beforeCommand" = "Runs "; +"settings.brew.autoremoveDesc.afterCommand" = " to uninstall formulae that were installed only as dependencies and are no longer needed by anything."; +"settings.brew.autoremoveConfirm.beforeCommand" = "This runs "; +"settings.brew.autoremoveConfirm.afterCommand" = ", which uninstalls formulae that were installed only as dependencies and are no longer required by anything else."; +"settings.liveEnrichment.title" = "Live category & description updates"; +"settings.liveEnrichment.hint.beforeHost" = "brew-browser ships with a built-in set of AI categories and descriptions. When on, it refreshes them from "; +"settings.liveEnrichment.hint.afterHost" = ": a tiny version check on refresh, the full category list when it's newer, and a per-package description when you open that package's detail. Only the package name you're viewing is sent (one HTTP GET); no IP is logged at the server, no cookies. Same first-party host as Enhanced Trending - a distinct trust boundary from the always-on Homebrew paths. Requires AI Features on."; +"settings.liveEnrichment.offline" = "Offline Mode is on - this toggle is locked off. Turn Offline Mode off above to enable live updates."; +"settings.trendingHistory.title" = "Enhanced Trending History"; +"settings.trendingHistory.hint.beforeHost" = "When on, brew-browser fetches per-package historical install trends from "; +"settings.trendingHistory.hint.afterHost" = " to power per-row sparklines on the Trending tab and a chart on each package's detail panel. Only the package name you're viewing is sent (one HTTP GET per package); no IP is logged at the server, no cookies, no fingerprinting. The endpoint is operated by the brew-browser project - a distinct trust boundary from the always-on Homebrew analytics paths above."; +"settings.trendingHistory.offline" = "Offline Mode is on - this toggle is locked off. Turn Offline Mode off above to enable history fetching."; +"settings.about.zeroTelemetryBody" = "brew-browser does not collect telemetry. It does not phone home. It does not have user accounts. Every outbound network request is documented in Settings -> Network and the README, and only fires when you take an action that requires it."; +"about.closeAria" = "Close About"; +"about.tagline" = "A native macOS GUI for Homebrew."; +"about.creditsBody.beforeClaude" = " - the multi-agent toolkit that orchestrated the waves (Backend Architect, Frontend Developer, Security Engineer, Code Reviewer, Technical Writer, and friends), powered by "; +"about.creditsBody.afterClaude" = " in the terminal, running "; +"about.thanksLinux" = "Thanks also to the Homebrew project for the package data, every formula maintainer for their work, and Tauri for the native shell."; +"about.thanksMac" = "Thanks also to the Homebrew project for the package data, every formula and cask maintainer for their work, and Tauri for the native shell."; +"about.posture" = "Zero telemetry. Zero accounts. Every outbound network call is documented in Settings -> Network."; +"Repo" = "Repo"; +"Built with" = "Built with"; +"Open GitHub Sponsors in your browser" = "Open GitHub Sponsors in your browser"; +"Close detail panel" = "Close detail panel"; +"Loading package detail…" = "Loading package detail…"; +"Couldn't load detail" = "Couldn't load detail"; +"Installed by User" = "Installed by User"; +"On request" = "On request"; +"Total on-disk size of the installed keg (all versions)." = "Total on-disk size of the installed keg (all versions)."; +"About categories" = "About categories"; +"About this package's categories" = "About this package's categories"; +"About tags" = "About tags"; +"About these tags" = "About these tags"; +"About this summary" = "About this summary"; +"detail.aboutCategories.body" = "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if a category looks off and we'll fix it in the next release."; +"detail.aboutTags.body" = "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if a tag looks off and we'll fix it in the next release."; +"detail.aboutSummary.body" = "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if the summary looks off and we'll fix it in the next release."; +"No longer available via Homebrew." = "No longer available via Homebrew."; +"May be removed in a future Homebrew update." = "May be removed in a future Homebrew update."; +"Run brew upgrade for this package" = "Run brew upgrade for this package"; +"Open advisory" = "Open advisory"; +"Patched version" = "Patched version"; +"detail.trend.bootstrap" = "Bootstrap + daily snapshots - granularity grows over time"; +"detail.trend.daily" = "Daily install snapshots"; +"Use cases" = "Use cases"; +"About use cases" = "About use cases"; +"About these use cases" = "About these use cases"; +"detail.aboutUseCases.body" = "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if these use cases look off and we'll fix them in the next release."; +"About similar packages" = "About similar packages"; +"About these similar packages" = "About these similar packages"; +"detail.aboutSimilar.body" = "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if these suggestions look off and we'll fix them in the next release."; +"GitHub repository statistics" = "GitHub repository statistics"; +"Couldn't load GitHub stats" = "Couldn't load GitHub stats"; +"Dependencies" = "Dependencies"; +"Required by" = "Required by"; +"About reverse dependencies" = "About reverse dependencies"; +"detail.aboutReverseDependencies.body" = "Packages in the bundled catalog that list this one as a dependency. Computed offline by inverting the catalog graph - no network or brew calls. Build-only and opt-in (recommended/optional) edges are labelled; everything else is a required runtime dependency."; +"Conflicts with" = "Conflicts with"; +"detail.action.reinstall" = "Reinstall"; +"detail.category.openTitle" = "Browse all packages in {category}"; +"detail.caskUnavailableLinux" = "Casks require macOS — this package can't be installed on Linux."; +"detail.deprecation.disabled.body" = "This package is disabled and is no longer available — it will be removed."; +"detail.deprecation.deprecated.body" = "This package is deprecated and scheduled for removal. It still installs for now."; +"detail.deprecation.disabledDate.format" = "Disabled: %@"; +"detail.deprecation.deprecatedDate.format" = "Deprecated: %@"; +"detail.deprecation.disabled.tooltip" = "Disabled — this package is no longer available and will be removed."; +"detail.deprecation.deprecated.tooltip" = "Deprecated — still installable, but scheduled for removal."; +"detail.deprecation.useInstead" = "Use instead:"; +"about.title" = "About Brew Browser"; +"action.cancel" = "Cancel"; +"action.close" = "Close"; +"action.deselectAll" = "Deselect all"; +"action.installing" = "Installing…"; +"action.refresh" = "Refresh"; +"action.retry" = "Retry"; +"action.selectAll" = "Select all"; +"activity.collapse" = "Collapse activity"; +"activity.copyOutput" = "Copy output"; +"activity.drawer.hide" = "Hide Activity drawer"; +"activity.drawer.show" = "Show Activity drawer"; +"activity.expand" = "Expand activity"; +"activity.keepJobs.format" = "Keep last %d completed jobs"; +"activity.limits.help" = "These limits apply to future saved jobs only. Existing history is not shortened retroactively."; +"activity.linesPerJob.format" = "Keep last %d log lines per job"; +"activity.notifyOnCompletion" = "Notify when jobs complete"; +"activity.notifyOnCompletion.help" = "Shows a macOS notification when a long-running brew job finishes."; +"chrome.donate.help" = "Support brew-browser via GitHub Sponsors"; +"chrome.donate.menu" = "Sponsor brew-browser…"; +"icon.source.faviconFrom" = "Favicon from {homepage}"; +"icon.source.homepage" = "homepage"; +"icon.source.installedApp" = "installed app"; +"icon.source.none" = "none"; +"enrichment.fetchLatest" = "Fetch latest categories & descriptions"; +"enrichment.fetchLatest.help" = "brew-browser ships with built-in AI categories and descriptions. When enabled, it checks for newer first-party enrichment data and fetches a package description when you open the package detail. Only the viewed package name is sent."; +"filter.all" = "All"; +"filter.dependency" = "Dependencies"; +"filter.manual" = "Manual"; +"filter.outdated" = "Outdated"; +"filter.vulnerable" = "Vulnerable"; +"github.connectedAs.format" = "GitHub: connected as @%@"; +"github.deviceFlowInstructions.format" = "Open %@ and enter this code. It has been copied to your clipboard."; +"github.keychain.help" = "The token is stored in macOS Keychain. Sign-in is optional and uses the minimum scopes needed for stars, watches, and issues."; +"github.noInstalledHomepages" = "None of your installed packages have a GitHub homepage."; +"github.signIn" = "Sign in"; +"github.signIn.help" = "Sign in with GitHub to star, watch, and file package metadata issues from brew-browser."; +"github.signInWithGitHub" = "Sign in with GitHub"; +"github.signOut" = "Sign out"; +"github.signedInAs" = "Signed in as"; +"github.signedInAsToast" = "Signed in as @{username}"; +"github.signedInToast" = "Signed in to GitHub"; +"github.signInDenied" = "GitHub sign-in denied"; +"github.signInExpired" = "Sign-in code expired"; +"github.signInExpiredBody" = "Try again."; +"github.stargazers" = "Stargazers"; +"github.forks" = "Forks"; +"github.licenseMismatchTitle" = "brew reports: {brew} · GitHub reports: {github}"; +"github.scopeRequired.title.format" = "%@ needs more access"; +"github.scopeRequired.body.format" = "Needs the \"%@\" GitHub permission. Click to grant it without signing out."; +"github.yourCode" = "Your code"; +"brew.status.checking" = "Checking Homebrew…"; +"brew.status.installed" = "Homebrew is installed."; +"brew.status.notFound" = "brew not found"; +"brew.status.notFoundOnPath" = "Homebrew not found on PATH."; +"brew.status.ready.format" = "Homebrew %@ · prefix %@"; +"brew.status.unknown" = "Homebrew status unknown"; +"brew.status.unknownWithError" = "Homebrew status unknown - {error}"; +"catalog.refresh.noWritableDir" = "Couldn't locate a writable catalog directory."; +"catalog.refresh.failed.format" = "Catalog refresh failed: %@"; +"catalog.refresh.malformedResponse" = "brew.sh returned an unexpected catalog format."; +"catalog.refresh.emptyResponse.format" = "brew.sh returned an empty catalog (formulae=%d, casks=%d)."; +"catalog.refresh.compression" = "Couldn't compress the refreshed catalog."; +"catalog.refresh.offlineBlocked" = "Offline mode is on — catalog refresh is blocked. Disable it in Settings → Network."; +"catalog.refresh.brewFailed" = "brew failed during refresh (exit {code})."; +"menu.go" = "Go"; +"package.kind.casks" = "Casks"; +"package.kind.formulae" = "Formulae"; +"package.pinned" = "Pinned"; +"palette.title.menu" = "Command Palette…"; +"security.brewVulnsInstalled" = "brew-vulns is installed"; +"security.brewVulnsMissing.help" = "Vulnerability scanning needs the official brew vulns subcommand."; +"security.installBrewVulns" = "Install brew-vulns"; +"security.offlineSuppressed" = "Offline Mode is on — vulnerability scanning is suppressed."; +"security.scanInstalled" = "Scan installed packages for known vulnerabilities"; +"security.scanInstalled.help" = "Opt-in, off by default. Runs the official brew vulns subcommand for installed formulae and stores findings locally."; +"security.viewVulnerablePackages" = "View vulnerable packages →"; +"detail.security.cleanStale" = "No advisories as of the last scan — re-scan to confirm"; +"detail.security.lastScan.format" = "Last scan: %@"; +"detail.security.notScanned.detail" = "Not scanned yet. Scanning checks every installed package — brew vulns can't scan just one."; +"detail.security.openAdvisory.format" = "Open advisory: %@"; +"detail.security.patchedIn.format" = "Patched in %@"; +"detail.security.rescanHelp" = "Runs brew vulns across your whole install — it can't scan a single package"; +"onboarding.aria" = "Homebrew setup"; +"onboarding.title" = "Let's get Homebrew set up"; +"onboarding.body.mac" = "brew-browser is a friendly face for Homebrew, the package manager for your Mac. Install it once and your library appears here automatically — no relaunch needed."; +"onboarding.body.linux" = "brew-browser is a friendly face for Homebrew, which runs on Linux too. Install it once and your library appears here automatically — no relaunch needed."; +"onboarding.clt.before" = "Install the Xcode Command Line Tools first: run"; +"onboarding.clt.after" = "in Terminal and follow the prompts."; +"onboarding.linuxDeps.before" = "Install Homebrew's build dependencies first — on Debian/Ubuntu:"; +"onboarding.linuxDeps.after" = "other distros: see the \"Homebrew on Linux\" page at docs.brew.sh."; +"onboarding.installCopied" = "Install command copied to clipboard"; +"onboarding.copyFailed" = "Couldn't copy command"; +"onboarding.openTerminalFailed" = "Couldn't open Terminal"; +"onboarding.commandCopiedFallback" = "The install command was copied instead — paste it into any terminal."; +"onboarding.openTerminalOrCopyFailed" = "Couldn't open Terminal or copy the command"; +"services.alreadyRunning" = "Already running"; +"services.empty.body" = "Install something like postgresql, redis, or nginx and services will show up here."; +"services.empty.title" = "No background services"; +"services.loadFailed" = "Couldn't load services"; +"services.loading" = "Loading brew services…"; +"services.notRunning" = "Not running"; +"services.restart" = "Restart service"; +"services.start" = "Start service"; +"services.status.error" = "error"; +"services.status.notLoaded" = "not loaded"; +"services.status.running" = "running"; +"services.status.scheduled" = "scheduled"; +"services.status.stopped" = "stopped"; +"services.status.unknown" = "unknown"; +"services.stop" = "Stop service"; +"settings.appearance.aiFeatures" = "AI features"; +"settings.appearance.aiFeatures.help" = "When off, only Homebrew's native metadata appears. AI-generated categories and descriptions are hidden."; +"settings.appearance.defaultLanding" = "Default landing"; +"settings.brew.advanced" = "Advanced"; +"settings.brew.analytics" = "Analytics"; +"settings.brew.analytics.help" = "Homebrew sends anonymous install analytics to formulae.brew.sh by default. This changes Homebrew's own analytics preference."; +"settings.brew.autoremove" = "Autoremove"; +"settings.brew.autoremove.confirmMessage" = "This runs brew autoremove to delete formulae installed only as dependencies and no longer needed by any package."; +"settings.brew.autoremove.confirmTitle" = "Remove unused dependencies?"; +"settings.brew.autoremove.failedToRun" = "brew autoremove failed to run"; +"settings.brew.autoremove.help" = "Runs brew autoremove to remove formulae that were installed only as dependencies and are no longer needed."; +"settings.brew.autoremoveUnused" = "Autoremove unused dependencies"; +"settings.brew.confirmDestructive" = "Confirm before uninstall / zap"; +"settings.brew.confirmDestructive.help" = "Ask for confirmation before irreversible actions: uninstalling a package, zap cleanup, or deleting a Brewfile."; +"settings.brew.confirmations" = "Confirmations"; +"settings.brew.greedyUpgrades" = "Greedy upgrades"; +"settings.brew.greedyUpgrades.help" = "Include self-updating casks such as Chrome when running brew upgrade --greedy. Off by default."; +"settings.brew.sendAnalytics" = "Send Homebrew install analytics"; +"settings.fileUnreadable" = "Settings file is unreadable."; +"settings.github.showStats" = "Show GitHub stats on package pages"; +"settings.github.showStats.help" = "When signed in, brew-browser checks whether you have starred packages with GitHub homepages."; +"settings.network.caskIconFetching" = "Cask icon fetching"; +"settings.network.catalogAutoRefresh" = "Catalog auto-refresh"; +"settings.network.catalogStaleThreshold.format" = "Show stale catalog banner after %d days"; +"settings.network.offlineMode" = "Offline Mode"; +"settings.network.offlineMode.help" = "Blocks all brew-browser network calls. brew commands you start still run normally."; +"settings.network.offlineMode.on" = "Offline Mode is on"; +"settings.network.trendingTtl.format" = "Trending cache TTL: %d minutes"; +"settings.offlineToggleLocked" = "Offline Mode is on — this toggle is locked."; +"settings.option.daily" = "Daily"; +"settings.option.installedOnly" = "Installed only"; +"settings.option.off" = "Off"; +"settings.option.weekly" = "Weekly"; +"settings.resetToDefaults" = "Reset to defaults"; +"settings.section.security" = "Security"; +"settings.section.updates" = "Updates"; +"table.actions" = "Actions"; +"table.name" = "Name"; +"table.sortBy" = "Sort by {label}"; +"table.sortedAsc" = "Sorted by {label}, ascending. Click to reverse."; +"table.sortedDesc" = "Sorted by {label}, descending. Click to reverse."; +"table.status" = "Status"; +"table.user" = "User"; +"dashboard.refresh.brewUpdateFailed" = "brew update finished with errors"; +"dashboard.refresh.catalogFailed" = "Catalog refresh failed"; +"dashboard.refresh.failed" = "Refresh failed"; +"dashboard.refresh.successTitle" = "Refreshed"; +"dashboard.refresh.successBody" = "brew taps + catalog + installed list all current"; +"dashboard.doctor.failedToRun" = "brew doctor failed to run"; +"dashboard.upgradeAll.success" = "Upgraded {count} packages"; +"storage.refreshDiskUsage" = "Refresh disk usage"; +"storage.diskProbeFailed" = "Disk probe failed: {error}"; +"storage.measuringDiskUsage" = "Measuring disk usage… (du -sk on {prefix})"; +"storage.notPresent" = "not present"; +"storage.revealInFinder" = "Reveal {path} in Finder"; +"storage.showInFileManager" = "Show {path} in file manager"; +"storage.revealLabelInFinder" = "Reveal {label} in Finder"; +"storage.showLabelInFileManager" = "Show {label} in file manager"; +"storage.pathMissing" = "Path doesn't exist"; +"resize.panel" = "Resize panel"; +"resize.dragHint" = "Drag to resize · double-click to reset"; +"theme.cycle" = "Cycle theme"; +"theme.label" = "Theme"; +"trending.fetchHistory" = "Fetch trending history"; +"trending.fetchHistory.help" = "Downloads per-package trend history for sparklines and package detail charts."; +"trending.sparklineAria" = "Install trend sparkline"; +"trending.error.backend" = "Backend not available: {error}"; +"trending.error.failed" = "Failed to load trending: {error}"; +"trending.error.network" = "Couldn't reach formulae.brew.sh"; +"updates.autoCheck" = "Auto-check daily"; +"updates.autoCheck.help" = "Automatically check for brew-browser updates once a day."; +"updates.available.help" = "Click to open the updater."; +"updates.channel" = "Channel"; +"updates.channel.label" = "Update channel"; +"updates.channel.stable" = "Stable"; +"updates.check.help" = "Checks the Sparkle update feed for a newer brew-browser build."; +"updates.checkForUpdates" = "Check for Updates…"; +"updates.checkNow" = "Check now"; +"updates.lastChecked.format" = "Last checked: %@"; +"updates.never" = "Never"; +"updates.noBeta" = "No beta channel in this release."; +"updates.offlineBlocked" = "Offline Mode blocks update checks."; +"upgrade.choosePackages" = "Choose packages to upgrade"; +"upgrade.noOutdated" = "No outdated packages."; +"upgrade.pinned.help" = "Pinned packages are not upgraded by brew. Unpin from the package detail page."; +"deprecated" = "deprecated"; +"discover.activeFilters" = "Active category filters"; +"discover.catalogFetched" = "Fetched from formulae.brew.sh"; +"discover.catalogRefreshed" = "Catalog refreshed"; +"discover.categoryBrowsingHidden" = "Category browsing is hidden because AI features are turned off in Settings → Appearance."; +"discover.categoryTileAria" = "{category} - {count} packages"; +"discover.deprecatedDefault" = "Deprecated - may be removed in a future Homebrew update."; +"discover.deprecatedReason" = "Deprecated: {reason}"; +"discover.disabledDefault" = "Disabled - no longer available via Homebrew."; +"discover.disabledReason" = "Disabled: {reason}"; +"discover.emptyFilter" = "No packages match this filter."; +"discover.groupPackagesLabel" = "Packages in {group}"; +"discover.loadingCategories" = "Loading categories…"; +"discover.noResults" = "Nothing matches \"{query}\"."; +"discover.noResults.body" = "Try a shorter or different term."; +"discover.noResultsInCategories" = "No \"{query}\" results in the selected categories."; +"discover.noResultsInCategories.body" = "Try removing a chip or broadening the search term."; +"discover.packagesInCategory" = "Packages in {category}"; +"discover.removeFilter" = "Remove {category} filter"; +"discover.searchAria" = "Search Homebrew"; +"discover.searchFailed" = "Search failed"; +"discover.searchPlaceholder" = "Search the Homebrew index…"; +"discover.searchPromptTitle" = "Type a query to search the Homebrew index"; +"discover.staleDismissAria" = "Dismiss catalog staleness banner"; +"discover.subgroupNote" = "grouped by overlapping category"; +"discover.subgroupsBody" = "Homebrew has no official sub-category data, so this is a co-occurrence grouping: within \"{category}\", packages are bucketed by the other categories they also belong to. A package with several overlapping categories appears in each bucket. \"General {category}\" holds packages tagged only \"{category}\" - it's often the largest group, and this is a grouping aid, not a strict taxonomy."; +"discover.subgroupsLabel" = "About sub-categories"; +"discover.subgroupsTitle" = "How these sub-groups are built"; +"package.installTrendFor" = "{name} install trend"; +"package.openToken" = "Open {token}"; +"recovery.reason.alreadyInstalled" = "{name} is already installed outside Homebrew."; +"recovery.reason.stillRequired" = "{name} is still required by another installed package."; +"vulnerabilities.scanFailed" = "Vulnerability scan failed"; +"vulnerabilities.scanOneFailed" = "Vulnerability scan failed for {name}"; +"settings.github.corruptSettings" = "Settings file unreadable - visit the Network section to reset."; +"settings.github.githubUserFallback" = "github user"; +"settings.github.privacy.afterStruct" = " view crosses the IPC boundary."; +"settings.github.privacy.beforeStruct" = "brew-browser stores your token in {keyringName}. The token is never sent over IPC to the renderer, never written to disk, and never logged. Only the derived "; +"settings.github.scopes.afterNotifications" = " to enable watching. No private-repo access, no email read, no admin."; +"settings.github.scopes.afterPublicRepo" = " to enable starring and issue filing, and "; +"settings.github.scopes.afterReadUser" = " to display your username, "; +"settings.github.scopes.beforeReadUser" = "Sign-in is optional. The minimum scopes requested are "; +"settings.github.signInHint" = "Opens GitHub's standard \"Authorize app\" flow in your browser. No password is ever entered into brew-browser. This is required to Star, Watch, and file issues on GitHub with brew-browser."; +"settings.github.signInPurpose" = "What sign-in is used for"; +"settings.github.signedInAs" = "Signed in as"; +"settings.github.statsHint.afterHost" = "."; +"settings.github.statsHint.beforeHost" = "Show repo stars, forks, and last release for any package whose homepage is a GitHub URL. brew-browser fetches public repo metadata from "; +"settings.github.scopesLabel" = "Scopes"; diff --git a/native/Sources/BrewBrowser/Resources/ru.lproj/Localizable.strings b/native/Sources/BrewBrowser/Resources/ru.lproj/Localizable.strings new file mode 100644 index 0000000..9bee143 --- /dev/null +++ b/native/Sources/BrewBrowser/Resources/ru.lproj/Localizable.strings @@ -0,0 +1,830 @@ +"AI & ML" = "ИИ и ML"; +"Browsers" = "Браузеры"; +"Cloud & DevOps" = "Облако и DevOps"; +"Communication" = "Коммуникации"; +"Data" = "Данные"; +"Developer Tools" = "Инструменты разработчика"; +"Editors & IDEs" = "Редакторы и IDE"; +"Education" = "Образование"; +"Games & Entertainment" = "Игры и развлечения"; +"Graphics & Design" = "Графика и дизайн"; +"Music" = "Музыка"; +"Office & Docs" = "Офис и документы"; +"Productivity" = "Продуктивность"; +"System Utilities" = "Системные утилиты"; +"Video & Audio" = "Видео и аудио"; +"Writing" = "Работа с текстом"; +"Uncategorized" = "Без категории"; +"app.controls" = "Элементы управления приложением"; +"chrome.github.connectionStatus" = "Статус подключения GitHub"; +"chrome.donate.aria" = "Поддержать brew-browser через GitHub Sponsors"; +"chrome.donate.title" = "Поддержать через GitHub Sponsors"; +"command.openActivity" = "Открыть активность"; +"command.openDiscover" = "Открыть каталог"; +"command.openLibrary" = "Открыть библиотеку"; +"command.openSnapshots" = "Открыть снимки"; +"command.openTrending" = "Открыть тренды"; +"command.refreshLibrary" = "Обновить библиотеку"; +"command.refreshSnapshots" = "Обновить снимки"; +"command.refreshTrending" = "Обновить тренды"; +"command.toggleActivityDrawer" = "Показать или скрыть журнал активности"; +"github.connectedAs" = "GitHub: выполнен вход как @{username}"; +"github.scopeIncomplete" = "GitHub: вход выполнен, но не хватает разрешений — нажмите, чтобы исправить"; +"keyboard.close" = "закрыть"; +"keyboard.navigate" = "перейти"; +"keyboard.open" = "открыть"; +"language.english" = "Английский"; +"language.hint.reload" = "После смены языка brew-browser перезагрузится."; +"language.label" = "Язык"; +"language.russian" = "Русский"; +"language.system" = "Язык системы"; +"nav.activity" = "Активность"; +"nav.dashboard" = "Сводка"; +"nav.discover" = "Каталог"; +"nav.library" = "Библиотека"; +"nav.primary" = "Основная навигация"; +"nav.services" = "Службы"; +"nav.snapshots" = "Снимки"; +"nav.trending" = "Тренды"; +"nav.showSidebar" = "Показать боковую панель"; +"nav.hideSidebar" = "Скрыть боковую панель"; +"package.installed" = "установлен"; +"package.kind.cask" = "cask-пакет"; +"package.kind.formula" = "формула"; +"palette.empty" = "Ничего не найдено."; +"palette.group.actions" = "Действия"; +"palette.group.commands" = "Команды"; +"palette.group.index" = "Каталог"; +"palette.group.installed" = "Установленные"; +"palette.results" = "Результаты палитры команд"; +"palette.search.placeholder" = "Введите команду, пакет или раздел."; +"palette.searchLabel" = "Поиск в палитре команд"; +"palette.title" = "Палитра команд"; +"palette.group.view" = "Вид"; +"search.allResultsInDiscover" = "Все результаты в Каталоге →"; +"search.clear" = "Очистить"; +"search.clearSearch" = "Очистить поиск"; +"search.noMatches" = "По запросу «{query}» ничего не найдено."; +"search.packages" = "Поиск пакетов"; +"search.packagesPlaceholder" = "Поиск пакетов…"; +"search.results" = "Результаты поиска"; +"search.searching" = "Идёт поиск…"; +"settings.open" = "Открыть настройки"; +"settings.close" = "Закрыть настройки"; +"settings.paneLabel" = "Настройки — {section}"; +"settings.sections" = "Разделы настроек"; +"settings.section.about" = "О приложении"; +"settings.section.activity" = "Активность"; +"settings.section.appearance" = "Оформление"; +"settings.section.brew" = "Brew"; +"settings.section.github" = "GitHub"; +"settings.section.network" = "Сеть"; +"settings.shortcutTitle" = "Настройки (⌘,)"; +"settings.title" = "Настройки"; +"theme.change" = "Изменить тему"; +"theme.dark" = "Тёмная"; +"theme.light" = "Светлая"; +"theme.title" = "Тема: {theme}"; +"theme.system" = "Системная"; +"trending.window.30d" = "30 дн."; +"trending.window.90d" = "90 дн."; +"trending.window.365d" = "365 дн."; +"updates.available" = "Доступно обновление"; +"updates.availableLabel" = "Доступно обновление: brew-browser {version}. Нажмите, чтобы открыть Настройки."; +"updates.dismissVersion" = "Скрыть уведомление об обновлении до v{version}"; +"updates.installing" = "Устанавливаем обновление"; +"updates.skipVersion" = "Пропустить эту версию"; +"updates.versionAvailable" = "Доступна версия brew-browser {version}"; +"vulnerability.packages" = "Уязвимых пакетов: {count}"; +"About" = "О приложении"; +"About Brew Browser" = "О Brew Browser"; +"Activity" = "Активность"; +"Actions" = "Действия"; +"Advanced" = "Дополнительно"; +"AI features" = "Функции ИИ"; +"All" = "Все"; +"All Categories" = "Все категории"; +"All current" = "Всё актуально"; +"Analytics" = "Аналитика"; +"Appearance" = "Оформление"; +"App version" = "Версия приложения"; +"Autoremove" = "Удалить лишние зависимости"; +"Autoremove unused dependencies" = "Удалить неиспользуемые зависимости"; +"Auto-check daily" = "Проверять ежедневно"; +"Body (Markdown)" = "Текст (Markdown)"; +"Brew" = "Brew"; +"Browse" = "Обзор"; +"Cancel" = "Отмена"; +"Casks" = "Cask-пакеты"; +"Casks (Caskroom)" = "Cask-пакеты (Caskroom)"; +"Caveats" = "Примечания"; +"Check for updates now" = "Проверить обновления сейчас"; +"Check now" = "Проверить"; +"Choose packages to upgrade" = "Выберите пакеты для обновления"; +"Choose…" = "Выбрать…"; +"Categories unavailable" = "Категории недоступны"; +"Categories" = "Категории"; +"Clear" = "Очистить"; +"Clear completed" = "Очистить завершённые"; +"Clear Finished" = "Очистить завершённые"; +"Clear categories" = "Сбросить категории"; +"Clear category filter" = "Сбросить фильтр категорий"; +"Clear filter" = "Сбросить фильтр"; +"Click the code to copy it." = "Нажмите код, чтобы скопировать его."; +"Close" = "Закрыть"; +"Command Palette…" = "Палитра команд…"; +"Composition" = "Состав"; +"Confirm before uninstall / zap" = "Подтверждать удаление и полную очистку (zap)"; +"Confirmations" = "Подтверждения"; +"Contacting GitHub…" = "Связываемся с GitHub…"; +"Create" = "Создать"; +"Dashboard" = "Сводка"; +"Dark" = "Тёмная"; +"Default landing" = "Стартовый раздел"; +"Delete" = "Удалить"; +"Dependency" = "Зависимость"; +"Description" = "Описание"; +"Download cache" = "Кэш загрузок"; +"Deselect all" = "Снять выбор"; +"Discover" = "Каталог"; +"Dismiss" = "Скрыть"; +"Dismiss for this session" = "Скрыть до конца сеанса"; +"Donate to the project" = "Поддержать проект"; +"Enter this code:" = "Введите этот код:"; +"Esc" = "Esc"; +"Export…" = "Экспорт…"; +"Exposure" = "Уязвимости"; +"Fetch latest categories & descriptions" = "Обновлять категории и описания"; +"Fetch trending history" = "Загружать историю трендов"; +"File issue" = "Сообщить о проблеме"; +"File an issue" = "Сообщить о проблеме"; +"Filter" = "Фильтр"; +"Formulae" = "Формулы"; +"Formulae (Cellar)" = "Формулы (Cellar)"; +"GitHub" = "GitHub"; +"Greedy upgrades (include self-updating casks)" = "Обновлять также самообновляемые cask-пакеты"; +"Homebrew" = "Homebrew"; +"Icon source" = "Источник иконки"; +"Import…" = "Импорт…"; +"Install" = "Установить"; +"Install brew-vulns" = "Установить brew-vulns"; +"Install complete. Relaunch to use the new version." = "Установка завершена. Перезапустите приложение, чтобы перейти на новую версию."; +"Install trend" = "Динамика установок"; +"Installed" = "Установлено"; +"Installed only" = "Только установленные"; +"Installing…" = "Устанавливаем…"; +"instead" = "вместо"; +"Keep last N completed jobs" = "Хранить последние N завершённых задач"; +"Language" = "Язык"; +"Labels" = "Метки"; +"Latest" = "Актуальная версия"; +"Library" = "Библиотека"; +"License" = "Лицензия"; +"License mismatch" = "Лицензии не совпадают"; +"Light" = "Светлая"; +"Lines per job" = "Строк на задачу"; +"Loading GitHub stats…" = "Загружаем статистику GitHub…"; +"Loading settings…" = "Загружаем настройки…"; +"Loading sign-in status…" = "Проверяем вход…"; +"Logs (var/log)" = "Логи (var/log)"; +"Manual" = "Вручную"; +"Name" = "Имя"; +"Network" = "Сеть"; +"New Snapshot" = "Новый снимок"; +"No beta channel in this release." = "В этом релизе нет бета-канала."; +"No background services" = "Фоновых служб нет"; +"No known vulnerabilities." = "Известных уязвимостей нет."; +"No known vulnerabilities" = "Известных уязвимостей нет"; +"No outdated packages." = "Устаревших пакетов нет."; +"No results." = "Ничего не найдено."; +"No snapshots yet" = "Снимков пока нет"; +"Not enough data yet" = "Пока недостаточно данных"; +"Not scanned yet." = "Проверка ещё не выполнялась."; +"Offline Mode" = "Офлайн-режим"; +"Off" = "Выкл."; +"OK" = "OK"; +"Open in browser" = "Открыть в браузере"; +"Open in Finder" = "Показать в Finder"; +"Outbound paths (read-only)" = "Исходящие сетевые обращения (только чтение)"; +"Package detail" = "Карточка пакета"; +"Patched in" = "Исправлено в"; +"Pinned" = "Закреплён"; +"Pinned packages are not upgraded by brew. Unpin from the package detail page." = "Закреплённые пакеты не обновляются через brew. Открепите пакет в его карточке."; +"Recent" = "Недавние"; +"RECENT" = "НЕДАВНИЕ"; +"Refresh" = "Обновить"; +"Refresh from brew.sh →" = "Обновить с brew.sh →"; +"Refreshing…" = "Обновляем…"; +"Remove" = "Убрать"; +"Repository" = "Репозиторий"; +"Reset to defaults" = "Сбросить настройки"; +"Resize package detail panel" = "Изменить ширину карточки пакета"; +"Restore" = "Восстановить"; +"Retry" = "Повторить"; +"Re-scan all" = "Проверить всё заново"; +"Scan all packages" = "Проверить все пакеты"; +"Scan installed packages for known vulnerabilities" = "Проверять установленные пакеты на известные уязвимости"; +"Scan now" = "Проверить сейчас"; +"Scanning all…" = "Проверяем все пакеты…"; +"Scanning…" = "Проверяем…"; +"Scrub — also remove the latest versions' cached downloads (more aggressive)" = "Тщательная очистка: удалить также кэш загрузок последних версий (агрессивнее)"; +"Search" = "Поиск"; +"Search packages…" = "Поиск пакетов…"; +"Searching…" = "Идёт поиск…"; +"Security" = "Безопасность"; +"Select all" = "Выбрать все"; +"Send Homebrew install analytics" = "Отправлять аналитику установок Homebrew"; +"Service" = "Служба"; +"Services" = "Службы"; +"Settings" = "Настройки"; +"Settings file is unreadable." = "Файл настроек не читается."; +"Show GitHub stats on package pages" = "Показывать статистику GitHub на страницах пакетов"; +"Show sidebar" = "Показать боковую панель"; +"Hide sidebar" = "Скрыть боковую панель"; +"Sign in" = "Вход"; +"Sign in to GitHub" = "Войти в GitHub"; +"Sign in with GitHub" = "Войти через GitHub"; +"Sign out" = "Выйти"; +"Sign-in denied." = "Вход отклонён."; +"Similar packages" = "Похожие пакеты"; +"Size" = "Размер"; +"Snapshot" = "Снимок"; +"Snapshots" = "Снимки"; +"Stable" = "Стабильный"; +"Start" = "Запустить"; +"Stop" = "Остановить"; +"Storage" = "Хранилище"; +"Status" = "Статус"; +"Submit" = "Отправить"; +"Support brew-browser via GitHub Sponsors" = "Поддержать brew-browser через GitHub Sponsors"; +"System" = "Системная"; +"System language" = "Язык системы"; +"Tags" = "Теги"; +"Tap" = "Tap"; +"Terminal" = "Терминал"; +"Theme" = "Тема"; +"Title" = "Заголовок"; +"Token" = "Токен"; +"Top categories in your library" = "Главные категории в библиотеке"; +"Trending" = "Тренды"; +"Trending cache TTL" = "TTL кэша трендов"; +"Try again" = "Попробовать снова"; +"Type" = "Тип"; +"Type a command, package, or section." = "Введите команду, пакет или раздел."; +"Update available" = "Доступно обновление"; +"Update channel" = "Канал обновлений"; +"Updates" = "Обновления"; +"Updates available" = "Доступны обновления"; +"Vulnerable" = "Уязвимые"; +"Velocity" = "Динамика"; +"Upgrade" = "Обновить"; +"Upgrade all" = "Обновить всё"; +"Upgrade available" = "Доступно обновление"; +"Upgrade to fix" = "Обновить, чтобы исправить"; +"Version" = "Версия"; +"Verbose — list every file removed" = "Подробный вывод: перечислить все удаляемые файлы"; +"View vulnerable packages →" = "Показать уязвимые пакеты →"; +"Waiting for authorization…" = "Ожидаем авторизации…"; +"Watch" = "Отслеживать"; +"Watching" = "Отслеживается"; +"Why install this?" = "Зачем это устанавливать?"; +"Window" = "Окно"; +"Window vibrancy" = "Эффект прозрачности окна"; +"— all enrichment is baked into the app binary." = "— всё уже включено в приложение."; +"Zero LLM calls are made from your machine" = "С вашего компьютера не выполняются LLM-запросы"; +"Zero telemetry. Zero accounts." = "Никакой телеметрии. Никаких учётных записей."; +"built with" = "создано с помощью"; +"close" = "закрыть"; +"close Esc" = "Закрыть (Esc)"; +"dependency" = "зависимость"; +"formula" = "формула"; +"cask" = "cask"; +"installed" = "установлено"; +"not present" = "нет на диске"; +"pinned" = "закреплён"; +"source" = "источник"; +"unavailable" = "недоступно"; +"working…" = "выполняется…"; +"Adopt existing" = "Использовать существующую копию"; +"Backend not available" = "Бэкенд недоступен"; +"Catalog refresh failed" = "Не удалось обновить каталог"; +"Change theme" = "Изменить тему"; +"Changing the language reloads brew-browser." = "После смены языка brew-browser перезагрузится."; +"Cleanup failed" = "Не удалось очистить кэш"; +"Clean up cache…" = "Очистить кэш…"; +"Clean up" = "Очистить"; +"Cleaning…" = "Очищаем…"; +"Cleaning up Homebrew cache" = "Очищаем кэш Homebrew"; +"Command palette" = "Палитра команд"; +"Command palette search" = "Поиск в палитре команд"; +"Command palette results" = "Результаты палитры команд"; +"Couldn't change brew analytics" = "Не удалось изменить аналитику brew"; +"Couldn't load services" = "Не удалось загрузить службы"; +"Couldn't load snapshots" = "Не удалось загрузить снимки"; +"Couldn't load packages." = "Не удалось загрузить пакеты."; +"Couldn't open in file manager" = "Не удалось открыть в файловом менеджере"; +"Couldn't reveal in Finder" = "Не удалось показать в Finder"; +"Disk probe failed" = "Не удалось проверить диск"; +"Donate via GitHub Sponsors" = "Поддержать через GitHub Sponsors"; +"Donate to brew-browser on GitHub Sponsors" = "Поддержать brew-browser через GitHub Sponsors"; +"Everything is up to date" = "Всё актуально"; +"English" = "Английский"; +"Follows the macOS theme when set to System." = "Следует теме macOS, если выбрана системная тема."; +"Failed to load packages" = "Не удалось загрузить пакеты"; +"Force remove" = "Удалить принудительно"; +"GitHub connection status" = "Статус подключения GitHub"; +"Homebrew analytics disabled" = "Аналитика Homebrew отключена"; +"Homebrew analytics enabled" = "Аналитика Homebrew включена"; +"Homebrew was not found on this Mac." = "Homebrew не найден на этом Mac."; +"Index" = "Индекс"; +"Installed as a dependency, not requested directly" = "Установлено как зависимость, а не вручную"; +"Invalid URL" = "Недопустимый URL"; +"Loading your packages…" = "Загружаем пакеты…"; +"Measuring disk usage…" = "Считаем место на диске…"; +"Nothing's run yet." = "Пока ничего не запускалось."; +"Open Activity" = "Открыть активность"; +"Open Discover" = "Открыть каталог"; +"Open Library" = "Открыть библиотеку"; +"Open Settings" = "Открыть настройки"; +"Open Snapshots" = "Открыть снимки"; +"Open Trending" = "Открыть тренды"; +"Open Library, filter outdated" = "Открыть библиотеку с фильтром устаревших"; +"Other" = "Другое"; +"Pick which packages to upgrade" = "Выбрать пакеты для обновления"; +"Refresh Library" = "Обновить библиотеку"; +"Refresh Snapshots" = "Обновить снимки"; +"Refresh Trending" = "Обновить тренды"; +"Refresh disk usage" = "Обновить сведения о диске"; +"Refreshed" = "Обновлено"; +"Report an issue on GitHub" = "Сообщить о проблеме на GitHub"; +"Report to brew-browser" = "Отправить отчёт в brew-browser"; +"Remove from history" = "Убрать из истории"; +"Remove it anyway, ignoring packages that depend on it." = "Удалить принудительно, игнорируя пакеты, которые от него зависят."; +"Remove unused dependencies?" = "Удалить неиспользуемые зависимости?"; +"Removing unused dependencies" = "Удаляем неиспользуемые зависимости"; +"Removing…" = "Удаляем…"; +"Replace the existing copy with a fresh Homebrew install." = "Заменить существующую копию новой установкой из Homebrew."; +"Run brew doctor" = "Запустить brew doctor"; +"Run brew doctor and stream the diagnostics into Activity" = "Запустить brew doctor и вывести диагностику в панель активности"; +"Run brew update, refresh the catalog from formulae.brew.sh, and reload your installed list" = "Запустить brew update, обновить каталог с formulae.brew.sh и перечитать список установленных пакетов"; +"Russian" = "Русский"; +"Search packages" = "Поиск пакетов"; +"Search results" = "Результаты поиска"; +"See the Activity drawer." = "Подробности в панели Активность."; +"Settings (⌘,)" = "Настройки (⌘,)"; +"Settings sections" = "Разделы настроек"; +"Submit issue" = "Отправить проблему"; +"This will install packages from the snapshot. Existing packages are skipped." = "Будут установлены пакеты из снимка. Уже установленные пакеты пропускаются."; +"Toggle Activity drawer" = "Показать или скрыть журнал активности"; +"Upgrade-all failed" = "Не удалось обновить всё"; +"Upgrading…" = "Обновляем…"; +"When off, only Homebrew's native metadata appears. Categories (sidebar tile grid, donut chart, chip filters) are also hidden because they're AI-generated." = "Если выключено, отображаются только штатные метаданные Homebrew. Категории (плитки в боковой панели, кольцевая диаграмма и фильтры по категориям) тоже скрываются, потому что они сгенерированы ИИ."; +"When on, brew-browser shows extra metadata generated at build time by AI: friendly names, expanded descriptions, use cases, similar package suggestions, and category tags." = "Если включено, brew-browser показывает дополнительные метаданные, сгенерированные ИИ при сборке: понятные названия, расширенные описания, сценарии использования, похожие пакеты и теги категорий."; +"Which section opens when you launch brew-browser." = "Какой раздел открывается при запуске brew-browser."; +"Requires app restart to take effect. The default (HudWindow) matches the rest of macOS." = "Вступит в силу после перезапуска приложения. Значение по умолчанию (HudWindow) соответствует остальной macOS."; +"all packages" = "все пакеты"; +"brew commands kicked off from here will show up in this list." = "Команды brew, запущенные из приложения, появятся в этом списке."; +"disabled" = "отключено"; +"Advisory" = "Бюллетень безопасности"; +"Catalog unavailable" = "Каталог недоступен"; +"Check this package for known vulnerabilities." = "Проверить этот пакет на известные уязвимости."; +"Commands" = "Команды"; +"Couldn't load packages" = "Не удалось загрузить пакеты"; +"Couldn't load trending" = "Не удалось загрузить тренды"; +"Copy" = "Скопировать"; +"Copy the command to the clipboard" = "Скопировать команду в буфер обмена"; +"Deprecated" = "Устарело"; +"Disabled" = "Отключено"; +"Disabled by Offline Mode" = "Отключено офлайн-режимом"; +"Blocked by Offline Mode. Disable in Settings → Network." = "Заблокировано офлайн-режимом. Отключите его в разделе «Настройки → Сеть»."; +"Fetching install counts from formulae.brew.sh…" = "Загружаем счётчики установок с formulae.brew.sh…"; +"Homebrew isn't installed" = "Homebrew не установлен"; +"Install Homebrew" = "Установить Homebrew"; +"Install `brew vulns` to scan this package for known vulnerabilities." = "Установите `brew vulns`, чтобы проверить этот пакет на известные уязвимости."; +"Install the Xcode Command Line Tools" = "Установить Xcode Command Line Tools"; +"Installs" = "Установки"; +"Installs, upgrades, uninstalls, and updates show up here." = "Здесь отображаются установки, обновления пакетов, удаления и обновления Homebrew."; +"Loading snapshots…" = "Загружаем снимки…"; +"Loading the Homebrew catalog…" = "Загружаем каталог Homebrew…"; +"Members also categorized here" = "Пакеты, которые также входят в эту категорию"; +"Members of this category with no other category" = "Пакеты только из этой категории"; +"No activity yet" = "Активности пока нет"; +"No packages" = "Пакетов нет"; +"No results" = "Ничего не найдено"; +"No summary available — view details" = "Нет краткого описания — открыть подробности"; +"Not installed" = "Не установлено"; +"Offline Mode is on — trending needs formulae.brew.sh." = "Офлайн-режим включён — трендам нужен доступ к formulae.brew.sh."; +"Open Dashboard" = "Открыть сводку"; +"Open Services" = "Открыть службы"; +"Open Terminal" = "Открыть Терминал"; +"Outdated" = "Есть обновления"; +"Paste the official install command (from brew.sh) into Terminal:" = "Вставьте официальную команду установки с brew.sh в Терминал:"; +"Reading your Homebrew install…" = "Читаем конфигурацию Homebrew…"; +"Restart" = "Перезапустить"; +"Runs brew vulns across your whole install — it can't scan a single package" = "Запускает brew vulns для всей установки Homebrew; отдельный пакет проверить нельзя"; +"Save your current setup so you can restore it on another Mac. Snapshots live in ~/Library/Application Support/brew-browser/brewfiles/ — findable outside the app too." = "Сохраните текущую конфигурацию, чтобы восстановить её на другом Mac. Снимки лежат в ~/Library/Application Support/brew-browser/brewfiles/ — их можно найти и вне приложения."; +"Scanning all packages…" = "Проверяем все пакеты…"; +"Sign in to GitHub happens on first action." = "Вход в GitHub откроется при первом действии."; +"Sign in to GitHub to star this repository" = "Войдите в GitHub, чтобы поставить звезду репозиторию"; +"Sign in to GitHub to watch this repository" = "Войдите в GitHub, чтобы отслеживать репозиторий"; +"Snapshot error" = "Ошибка снимка"; +"Star" = "Поставить звезду"; +"Star this repository" = "Поставить звезду репозиторию"; +"Starred" = "Звезда поставлена"; +"Stop watching" = "Не отслеживать"; +"Stored in ~/Library/Application Support/brew-browser/brewfiles/" = "Хранится в ~/Library/Application Support/brew-browser/brewfiles/"; +"The Brewfile will be removed from disk. This cannot be undone." = "Brewfile будет удалён с диска. Это действие нельзя отменить."; +"The Homebrew installer needs them. Run this in Terminal and follow the prompt:" = "Они нужны установщику Homebrew. Выполните это в Терминале и следуйте подсказкам:"; +"The bundled package catalog couldn't be loaded." = "Не удалось загрузить встроенный каталог пакетов."; +"The brew-vulns subcommand isn't installed." = "Подкоманда brew-vulns не установлена."; +"settings.vulnerabilities.cleanResult" = "В установленных пакетах известные уязвимости не найдены."; +"settings.vulnerabilities.helperInstalledTitle" = "brew-vulns установлен"; +"settings.vulnerabilities.initialScanBody" = "Запускаем первичную проверку…"; +"settings.vulnerabilities.installFailedTitle" = "Не удалось установить brew-vulns"; +"settings.vulnerabilities.installPrompt" = "Для проверки уязвимостей нужна официальная подкоманда brew vulns. Установить её сейчас? Будет выполнено brew install homebrew/brew-vulns/brew-vulns."; +"settings.vulnerabilities.lastScan" = "Последняя проверка"; +"settings.vulnerabilities.offlineHint" = "Офлайн-режим включён — проверка уязвимостей заблокирована, даже если переключатель включён. Отключите офлайн-режим выше, чтобы разрешить проверку."; +"settings.vulnerabilities.privacyHint" = "Опционально, выключено по умолчанию. При включении brew-browser запускает официальную подкоманду brew vulns проекта Homebrew: она запрашивает OSV.dev (сервис Google) об известных уязвимостях в установленных формулах. Если вы также вошли в GitHub, отдельные GHSA-ID дополняются сведениями из api.github.com. Результаты кэшируются локально; список пакетов не покидает ваш компьютер, кроме запросов, которые сама brew vulns отправляет в OSV."; +"settings.vulnerabilities.scanAllTitle" = "Запустить brew vulns для всех установленных формул"; +"This installs packages from the snapshot. Existing packages are skipped." = "Будут установлены пакеты из снимка. Уже установленные пакеты пропускаются."; +"Use" = "Используйте"; +"User" = "Пользователь"; +"Uninstall" = "Удалить"; +"Unstar this repository" = "Убрать звезду с репозитория"; +"Vulnerability Scanning" = "Проверка уязвимостей"; +"Waiting for Homebrew… the app continues automatically once it's installed." = "Ожидаем Homebrew… приложение продолжит работу автоматически после установки."; +"Watch for activity" = "Отслеживать активность"; +"Working… (see Activity)" = "Выполняется… см. «Активность»"; +"Your browser opened to GitHub's device page. Enter this code (it's copied to your clipboard):" = "Браузер открыл страницу GitHub для ввода кода устройства. Введите этот код (он уже скопирован в буфер обмена):"; +"brew-browser hit an unexpected error running this command." = "brew-browser столкнулся с неожиданной ошибкой при запуске этой команды."; +"brew-browser is a window onto Homebrew — install it and the app takes it from there." = "brew-browser работает поверх Homebrew: установите Homebrew, а дальше приложение всё подхватит."; +"formulae.brew.sh returned no entries." = "formulae.brew.sh не вернул записей."; +"instead." = "вместо него."; +"Already running" = "Уже запущено"; +"App Store Version Detected" = "Обнаружена версия из App Store"; +"Cancel sign in" = "Отменить вход"; +"Click to copy" = "Нажмите, чтобы скопировать"; +"Code copied to clipboard" = "Код скопирован в буфер обмена"; +"Code expired. Please try again." = "Срок действия кода истёк. Попробуйте ещё раз."; +"Couldn't copy code" = "Не удалось скопировать код"; +"Delete failed" = "Не удалось удалить"; +"Deleted snapshot" = "Снимок удалён"; +"Export failed" = "Не удалось экспортировать"; +"Exported snapshot" = "Снимок экспортирован"; +"Import failed" = "Не удалось импортировать"; +"Imported snapshot" = "Снимок импортирован"; +"Install & Override" = "Установить с заменой"; +"Installation Blocked" = "Установка заблокирована"; +"Loading brew services…" = "Загружаем службы brew…"; +"No background services." = "Фоновых служб нет."; +"Install something like postgresql, redis, or nginx and they'll show up here." = "Установите, например, postgresql, redis или nginx — службы появятся здесь."; +"Refresh services" = "Обновить службы"; +"Restore complete" = "Восстановление завершено"; +"Restore failed" = "Не удалось восстановить"; +"Snapshot failed" = "Не удалось сохранить снимок"; +"Snapshot saved" = "Снимок сохранён"; +"Start service" = "Запустить службу"; +"Stop service" = "Остановить службу"; +"Restart service" = "Перезапустить службу"; +"Not running" = "Не запущено"; +"Brew services" = "Службы brew"; +"Open package details" = "Открыть карточку пакета"; +"Save your current setup so you can restore it on another machine. Snapshots live in ~/.local/share/brew-browser/brewfiles/ — findable outside the app too." = "Сохраните текущую конфигурацию, чтобы восстановить её на другой машине. Снимки лежат в ~/.local/share/brew-browser/brewfiles/ — их можно найти и вне приложения."; +"This will remove the package from your system." = "Пакет будет удалён из системы."; +"Overwrite manual installation?" = "Заменить ручную установку?"; +"An existing version was found in your Applications folder. If you proceed, Homebrew will force-install and overwrite the existing bundle." = "В папке /Applications уже есть эта версия. Если продолжить, Homebrew принудительно установит пакет и заменит существующее приложение."; +"An existing App Store version was found in your Applications folder." = "В папке /Applications уже есть версия, установленная через App Store."; +"App Store bundles are system-protected and cannot be overwritten by Homebrew." = "Приложения из App Store защищены системой, и Homebrew не сможет заменить их напрямую."; +"Resolution: delete the App Store version from /Applications, then install it again via Homebrew." = "Решение: удалите из /Applications версию, установленную через App Store, затем установите приложение через Homebrew."; +"Use the Sign in with GitHub button in Settings." = "Используйте кнопку «Войти через GitHub» в настройках."; +"Re-authorize" = "Обновить разрешения"; +"Upgrade failed" = "Не удалось обновить"; +"Upgraded packages" = "Пакеты обновлены"; +"Watching package" = "Отслеживаем пакет"; +"Stopped watching package" = "Отслеживание пакета отключено"; +"Starred package" = "Звезда поставлена"; +"Unstarred package" = "Звезда убрана"; +"Signed in as GitHub user." = "Выполнен вход в GitHub."; +"settings.activity.rangeDefault" = "Диапазон {min}–{max}. По умолчанию {default}."; +"settings.activity.retentionNote" = "Новые ограничения применяются только к задачам, которые будут сохранены после изменения настройки. Уже сохранённая история не сокращается задним числом."; +"settings.brew.analyticsDesc.beforeCommand" = "Homebrew по умолчанию отправляет анонимную аналитику установок на formulae.brew.sh. Этот переключатель меняет настройку Homebrew (то же, что выполнить "; +"settings.brew.analyticsDesc.afterCommand" = " в Терминале)."; +"settings.brew.analyticsToggle" = "Отправлять аналитику установок Homebrew"; +"settings.brew.confirmationsDesc" = "Перед необратимыми действиями (удаление пакета, zap-очистка, удаление Brewfile) приложение запрашивает подтверждение. Опытные пользователи могут отключить это, когда будут уверены."; +"settings.brew.advancedDesc.beforeCommand" = "Дополнительные параметры "; +"settings.brew.advancedDesc.afterCommand" = " для опытных пользователей."; +"settings.brew.greedyDesc.beforeFlag" = "Добавляет "; +"settings.brew.greedyDesc.between" = " к "; +"settings.brew.greedyDesc.afterCommand" = ", чтобы обновлялись и cask-пакеты с собственным механизмом обновления (например, Chrome). По умолчанию выключено: режим --greedy может лишний раз переустанавливать такие приложения."; +"settings.brew.autoremoveDesc.beforeCommand" = "Запускает "; +"settings.brew.autoremoveDesc.afterCommand" = ", чтобы удалить формулы, установленные только как зависимости и больше не нужные ни одному пакету."; +"settings.brew.autoremoveConfirm.beforeCommand" = "Будет выполнено "; +"settings.brew.autoremoveConfirm.afterCommand" = ": команда удалит формулы, установленные только как зависимости и больше не используемые другими пакетами."; +"settings.liveEnrichment.title" = "Свежие категории и описания"; +"settings.liveEnrichment.hint.beforeHost" = "brew-browser поставляется со встроенными ИИ-категориями и описаниями. Если включено, приложение обновляет их с "; +"settings.liveEnrichment.hint.afterHost" = ": при обновлении проверяет версию, загружает полный список категорий, если он новее, и получает описание пакета, когда вы открываете его карточку. Отправляется только имя просматриваемого пакета (один HTTP GET); сервер не логирует IP и не использует файлы cookie. Это собственный эндпоинт проекта brew-browser, отдельная зона доверия от постоянно доступных путей Homebrew. Требует включить функции ИИ."; +"settings.liveEnrichment.offline" = "Офлайн-режим включён — переключатель заблокирован. Отключите офлайн-режим выше, чтобы разрешить обновление категорий и описаний."; +"settings.trendingHistory.title" = "Расширенная история трендов"; +"settings.trendingHistory.hint.beforeHost" = "Если включено, brew-browser загружает историческую динамику установок по пакетам с "; +"settings.trendingHistory.hint.afterHost" = " для мини-графиков в строках вкладки «Тренды» и графика в карточке пакета. Отправляется только имя просматриваемого пакета (один HTTP GET на пакет); сервер не логирует IP, не использует файлы cookie и не собирает цифровой отпечаток. Этот эндпоинт поддерживает проект brew-browser — это отдельная зона доверия от постоянно доступных выше путей аналитики Homebrew."; +"settings.trendingHistory.offline" = "Офлайн-режим включён — переключатель заблокирован. Отключите офлайн-режим выше, чтобы разрешить загрузку истории."; +"settings.about.zeroTelemetryBody" = "brew-browser не собирает телеметрию, не обращается к своим серверам без необходимости и не использует учётные записи. Все исходящие сетевые запросы описаны в «Настройки → Сеть» и README и выполняются только для действий, которым они нужны."; +"about.closeAria" = "Закрыть окно «О приложении»"; +"about.tagline" = "Нативный интерфейс macOS для Homebrew."; +"about.creditsBody.beforeClaude" = " — многоагентный набор инструментов, который координировал волны работы (Backend Architect, Frontend Developer, Security Engineer, Code Reviewer, Technical Writer и другие), при участии "; +"about.creditsBody.afterClaude" = " в терминале, модель "; +"about.thanksLinux" = "Благодарим Homebrew за каталог пакетов, мейнтейнеров формул — за их работу, а Tauri — за нативную оболочку."; +"about.thanksMac" = "Благодарим Homebrew за каталог пакетов, мейнтейнеров формул и cask-пакетов — за их работу, а Tauri — за нативную оболочку."; +"about.posture" = "Никакой телеметрии. Никаких учётных записей. Все исходящие сетевые обращения описаны в «Настройки → Сеть»."; +"Repo" = "Репозиторий"; +"Built with" = "Создано с помощью"; +"Open GitHub Sponsors in your browser" = "Открыть GitHub Sponsors в браузере"; +"Close detail panel" = "Закрыть карточку пакета"; +"Loading package detail…" = "Загружаем карточку пакета…"; +"Couldn't load detail" = "Не удалось загрузить карточку"; +"Installed by User" = "Найдено вне Homebrew"; +"On request" = "Вручную"; +"Total on-disk size of the installed keg (all versions)." = "Общий размер установленного keg на диске (все версии)."; +"About categories" = "О категориях"; +"About this package's categories" = "О категориях этого пакета"; +"About tags" = "О тегах"; +"About these tags" = "Об этих тегах"; +"About this summary" = "Об описании"; +"detail.aboutCategories.body" = "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если категория выглядит неверно, и мы исправим её в следующем релизе."; +"detail.aboutTags.body" = "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если тег выглядит неверно, и мы исправим его в следующем релизе."; +"detail.aboutSummary.body" = "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если описание выглядит неверно, и мы исправим его в следующем релизе."; +"No longer available via Homebrew." = "Больше недоступно через Homebrew."; +"May be removed in a future Homebrew update." = "Может быть удалено при одном из будущих обновлений Homebrew."; +"Run brew upgrade for this package" = "Запустить brew upgrade для этого пакета"; +"Open advisory" = "Открыть бюллетень безопасности"; +"Patched version" = "Исправленная версия"; +"detail.trend.bootstrap" = "Начальные и ежедневные снимки — детализация растёт со временем"; +"detail.trend.daily" = "Ежедневные снимки установок"; +"Use cases" = "Сценарии использования"; +"About use cases" = "О сценариях использования"; +"About these use cases" = "Об этих сценариях использования"; +"detail.aboutUseCases.body" = "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если сценарии использования выглядят неверно, и мы исправим их в следующем релизе."; +"About similar packages" = "О похожих пакетах"; +"About these similar packages" = "Об этих похожих пакетах"; +"detail.aboutSimilar.body" = "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если рекомендации выглядят неверно, и мы исправим их в следующем релизе."; +"GitHub repository statistics" = "Статистика репозитория GitHub"; +"Couldn't load GitHub stats" = "Не удалось загрузить статистику GitHub"; +"Dependencies" = "Зависимости"; +"Required by" = "Зависимые пакеты"; +"About reverse dependencies" = "Об обратных зависимостях"; +"detail.aboutReverseDependencies.body" = "Пакеты во встроенном каталоге, где этот пакет указан как зависимость. Вычисляется офлайн инверсией графа каталога — без сетевых запросов и вызовов brew. Зависимости только для сборки и опциональные/рекомендуемые связи помечены; остальное — обязательные runtime-зависимости."; +"Conflicts with" = "Конфликтует с"; +"detail.action.reinstall" = "Переустановить"; +"detail.category.openTitle" = "Показать все пакеты в категории «{category}»"; +"detail.caskUnavailableLinux" = "Cask-пакеты требуют macOS — этот пакет нельзя установить в Linux."; +"detail.deprecation.disabled.body" = "Этот пакет отключён и больше недоступен — он будет удалён."; +"detail.deprecation.deprecated.body" = "Этот пакет устарел и запланирован к удалению. Пока его ещё можно установить."; +"detail.deprecation.disabledDate.format" = "Отключён: %@"; +"detail.deprecation.deprecatedDate.format" = "Устарел: %@"; +"detail.deprecation.disabled.tooltip" = "Отключено — пакет больше недоступен и будет удалён."; +"detail.deprecation.deprecated.tooltip" = "Устарело — пока устанавливается, но запланировано к удалению."; +"detail.deprecation.useInstead" = "Используйте вместо него:"; +"about.title" = "О Brew Browser"; +"action.cancel" = "Отмена"; +"action.close" = "Закрыть"; +"action.deselectAll" = "Снять выбор"; +"action.installing" = "Устанавливаем…"; +"action.refresh" = "Обновить"; +"action.retry" = "Повторить"; +"action.selectAll" = "Выбрать все"; +"activity.collapse" = "Свернуть активность"; +"activity.copyOutput" = "Скопировать вывод"; +"activity.drawer.hide" = "Скрыть журнал активности"; +"activity.drawer.show" = "Показать журнал активности"; +"activity.expand" = "Развернуть активность"; +"activity.keepJobs.format" = "Хранить последние %d завершённых задач"; +"activity.limits.help" = "Новые ограничения применяются только к задачам, которые будут сохранены после изменения настройки. Уже сохранённая история не сокращается задним числом."; +"activity.linesPerJob.format" = "Хранить последние %d строк лога на задачу"; +"activity.notifyOnCompletion" = "Уведомлять о завершении задач"; +"activity.notifyOnCompletion.help" = "Показывает уведомление macOS, когда долгая задача brew завершается."; +"chrome.donate.help" = "Поддержать brew-browser через GitHub Sponsors"; +"chrome.donate.menu" = "Поддержать brew-browser…"; +"icon.source.faviconFrom" = "Favicon с {homepage}"; +"icon.source.homepage" = "домашняя страница"; +"icon.source.installedApp" = "установленное приложение"; +"icon.source.none" = "нет"; +"enrichment.fetchLatest" = "Обновлять категории и описания"; +"enrichment.fetchLatest.help" = "brew-browser поставляется со встроенными ИИ-категориями и описаниями. Если включено, приложение проверяет наличие более свежих данных проекта и получает описание пакета, когда вы открываете его карточку. Отправляется только имя просматриваемого пакета."; +"filter.all" = "Все"; +"filter.dependency" = "Зависимости"; +"filter.manual" = "Вручную"; +"filter.outdated" = "Есть обновления"; +"filter.vulnerable" = "Уязвимые"; +"github.connectedAs.format" = "GitHub: выполнен вход как @%@"; +"github.deviceFlowInstructions.format" = "Откройте %@ и введите этот код. Он уже скопирован в буфер обмена."; +"github.keychain.help" = "Токен хранится в macOS Keychain. Вход необязателен и использует минимальные разрешения для звёзд, отслеживания и создания issue в GitHub."; +"github.noInstalledHomepages" = "У установленных пакетов нет домашней страницы GitHub."; +"github.signIn" = "Вход"; +"github.signIn.help" = "Войдите в GitHub, чтобы ставить звёзды, отслеживать репозитории и создавать issue в GitHub по метаданным пакетов из brew-browser."; +"github.signInWithGitHub" = "Войти через GitHub"; +"github.signOut" = "Выйти"; +"github.signedInAs" = "Выполнен вход как"; +"github.signedInAsToast" = "Выполнен вход как @{username}"; +"github.signedInToast" = "Выполнен вход в GitHub"; +"github.signInDenied" = "Вход в GitHub отклонён"; +"github.signInExpired" = "Срок действия кода истёк"; +"github.signInExpiredBody" = "Попробуйте ещё раз."; +"github.stargazers" = "Звёзды"; +"github.forks" = "Форки"; +"github.licenseMismatchTitle" = "brew сообщает: {brew} · GitHub сообщает: {github}"; +"github.scopeRequired.title.format" = "Нужны дополнительные разрешения: %@"; +"github.scopeRequired.body.format" = "Не хватает разрешения GitHub «%@». Нажмите, чтобы выдать его без выхода из аккаунта."; +"github.yourCode" = "Ваш код"; +"brew.status.checking" = "Проверяем Homebrew…"; +"brew.status.installed" = "Homebrew установлен."; +"brew.status.notFound" = "brew не найден"; +"brew.status.notFoundOnPath" = "Homebrew не найден в PATH."; +"brew.status.ready.format" = "Homebrew %@ · prefix %@"; +"brew.status.unknown" = "Статус Homebrew неизвестен"; +"brew.status.unknownWithError" = "Статус Homebrew неизвестен — {error}"; +"catalog.refresh.noWritableDir" = "Не удалось найти доступный для записи каталог."; +"catalog.refresh.failed.format" = "Не удалось обновить каталог: %@"; +"catalog.refresh.malformedResponse" = "brew.sh вернул каталог в неожиданном формате."; +"catalog.refresh.emptyResponse.format" = "brew.sh вернул пустой каталог (формулы=%d, cask-пакеты=%d)."; +"catalog.refresh.compression" = "Не удалось сжать обновлённый каталог."; +"catalog.refresh.offlineBlocked" = "Офлайн-режим включён — обновление каталога заблокировано. Отключите его в разделе «Настройки → Сеть»."; +"catalog.refresh.brewFailed" = "brew завершился с ошибкой при обновлении каталога (код {code})."; +"menu.go" = "Переход"; +"package.kind.casks" = "Cask-пакеты"; +"package.kind.formulae" = "Формулы"; +"package.pinned" = "Закреплён"; +"palette.title.menu" = "Палитра команд…"; +"security.brewVulnsInstalled" = "brew-vulns установлен"; +"security.brewVulnsMissing.help" = "Для проверки уязвимостей нужна официальная подкоманда brew vulns."; +"security.installBrewVulns" = "Установить brew-vulns"; +"security.offlineSuppressed" = "Офлайн-режим включён — проверка уязвимостей заблокирована."; +"security.scanInstalled" = "Проверять установленные пакеты на известные уязвимости"; +"security.scanInstalled.help" = "Опционально, выключено по умолчанию. Запускает официальную подкоманду brew vulns для установленных формул и хранит результаты локально."; +"security.viewVulnerablePackages" = "Показать уязвимые пакеты →"; +"detail.security.cleanStale" = "На момент последней проверки известных уязвимостей нет — проверьте ещё раз для подтверждения"; +"detail.security.lastScan.format" = "Последняя проверка: %@"; +"detail.security.notScanned.detail" = "Проверка ещё не выполнялась. Проверка проходит по всем установленным пакетам: brew vulns не умеет проверять только один пакет."; +"detail.security.openAdvisory.format" = "Открыть бюллетень безопасности: %@"; +"detail.security.patchedIn.format" = "Исправлено в %@"; +"detail.security.rescanHelp" = "Запускает brew vulns по всей установленной библиотеке; отдельный пакет проверить нельзя"; +"onboarding.aria" = "Настройка Homebrew"; +"onboarding.title" = "Настроим Homebrew"; +"onboarding.body.mac" = "brew-browser — удобный интерфейс для Homebrew, пакетного менеджера для Mac. Установите Homebrew один раз, и библиотека появится здесь автоматически — перезапуск не нужен."; +"onboarding.body.linux" = "brew-browser — удобный интерфейс для Homebrew, который работает и в Linux. Установите Homebrew один раз, и библиотека появится здесь автоматически — перезапуск не нужен."; +"onboarding.clt.before" = "Сначала установите Xcode Command Line Tools: выполните"; +"onboarding.clt.after" = "в Терминале и следуйте подсказкам."; +"onboarding.linuxDeps.before" = "Сначала установите зависимости сборки Homebrew. Для Debian/Ubuntu:"; +"onboarding.linuxDeps.after" = "для других дистрибутивов см. страницу \"Homebrew on Linux\" на docs.brew.sh."; +"onboarding.installCopied" = "Команда установки скопирована"; +"onboarding.copyFailed" = "Не удалось скопировать команду"; +"onboarding.openTerminalFailed" = "Не удалось открыть Терминал"; +"onboarding.commandCopiedFallback" = "Команда установки скопирована — вставьте её в любой терминал."; +"onboarding.openTerminalOrCopyFailed" = "Не удалось открыть Терминал или скопировать команду"; +"services.alreadyRunning" = "Уже запущено"; +"services.empty.body" = "Установите, например, postgresql, redis или nginx — службы появятся здесь."; +"services.empty.title" = "Фоновых служб нет"; +"services.loadFailed" = "Не удалось загрузить службы"; +"services.loading" = "Загружаем службы brew…"; +"services.notRunning" = "Не запущено"; +"services.restart" = "Перезапустить службу"; +"services.start" = "Запустить службу"; +"services.status.error" = "ошибка"; +"services.status.notLoaded" = "не загружена"; +"services.status.running" = "запущена"; +"services.status.scheduled" = "запланирована"; +"services.status.stopped" = "остановлена"; +"services.status.unknown" = "неизвестно"; +"services.stop" = "Остановить службу"; +"settings.appearance.aiFeatures" = "Функции ИИ"; +"settings.appearance.aiFeatures.help" = "Если выключено, отображаются только штатные метаданные Homebrew. ИИ-категории и описания скрываются."; +"settings.appearance.defaultLanding" = "Стартовый раздел"; +"settings.brew.advanced" = "Дополнительно"; +"settings.brew.analytics" = "Аналитика"; +"settings.brew.analytics.help" = "Homebrew по умолчанию отправляет анонимную аналитику установок на formulae.brew.sh. Этот переключатель меняет собственную настройку аналитики Homebrew."; +"settings.brew.autoremove" = "Удалить лишние зависимости"; +"settings.brew.autoremove.confirmMessage" = "Будет выполнено brew autoremove: команда удалит формулы, установленные только как зависимости и больше не нужные ни одному пакету."; +"settings.brew.autoremove.confirmTitle" = "Удалить неиспользуемые зависимости?"; +"settings.brew.autoremove.failedToRun" = "Не удалось запустить brew autoremove"; +"settings.brew.autoremove.help" = "Запускает brew autoremove, чтобы удалить формулы, установленные только как зависимости и больше не нужные ни одному пакету."; +"settings.brew.autoremoveUnused" = "Удалить неиспользуемые зависимости"; +"settings.brew.confirmDestructive" = "Подтверждать удаление и zap-очистку"; +"settings.brew.confirmDestructive.help" = "Перед необратимыми действиями (удаление пакета, zap-очистка, удаление Brewfile) приложение запрашивает подтверждение."; +"settings.brew.confirmations" = "Подтверждения"; +"settings.brew.greedyUpgrades" = "Расширенное обновление"; +"settings.brew.greedyUpgrades.help" = "Включает cask-пакеты с собственным механизмом обновления, например Chrome, при запуске brew upgrade --greedy. По умолчанию выключено."; +"settings.brew.sendAnalytics" = "Отправлять аналитику установок Homebrew"; +"settings.fileUnreadable" = "Файл настроек не читается."; +"settings.github.showStats" = "Показывать статистику GitHub на страницах пакетов"; +"settings.github.showStats.help" = "После входа brew-browser проверяет, какие пакеты с домашними страницами GitHub вы отметили звёздочкой."; +"settings.network.caskIconFetching" = "Загрузка иконок cask-пакетов"; +"settings.network.catalogAutoRefresh" = "Автообновление каталога"; +"settings.network.catalogStaleThreshold.format" = "Показывать предупреждение об устаревшем каталоге через %d дней"; +"settings.network.offlineMode" = "Офлайн-режим"; +"settings.network.offlineMode.help" = "Блокирует все сетевые запросы brew-browser. Команды brew, которые вы запускаете сами, продолжают работать как обычно."; +"settings.network.offlineMode.on" = "Офлайн-режим включён"; +"settings.network.trendingTtl.format" = "TTL кэша трендов: %d мин."; +"settings.offlineToggleLocked" = "Офлайн-режим включён — переключатель заблокирован."; +"settings.option.daily" = "Ежедневно"; +"settings.option.installedOnly" = "Только установленные"; +"settings.option.off" = "Выкл."; +"settings.option.weekly" = "Еженедельно"; +"settings.resetToDefaults" = "Сбросить настройки"; +"settings.section.security" = "Безопасность"; +"settings.section.updates" = "Обновления"; +"table.actions" = "Действия"; +"table.name" = "Имя"; +"table.sortBy" = "Сортировать по столбцу «{label}»"; +"table.sortedAsc" = "Сортировка по столбцу «{label}» по возрастанию. Нажмите, чтобы изменить порядок."; +"table.sortedDesc" = "Сортировка по столбцу «{label}» по убыванию. Нажмите, чтобы изменить порядок."; +"table.status" = "Статус"; +"table.user" = "Пользователь"; +"dashboard.refresh.brewUpdateFailed" = "brew update завершился с ошибками"; +"dashboard.refresh.catalogFailed" = "Не удалось обновить каталог"; +"dashboard.refresh.failed" = "Не удалось обновить"; +"dashboard.refresh.successTitle" = "Обновлено"; +"dashboard.refresh.successBody" = "tap-репозитории brew, каталог и список установленных пакетов актуальны"; +"dashboard.doctor.failedToRun" = "Не удалось запустить brew doctor"; +"dashboard.upgradeAll.success" = "Обновлено пакетов: {count}"; +"storage.refreshDiskUsage" = "Обновить сведения о месте на диске"; +"storage.diskProbeFailed" = "Не удалось проверить диск: {error}"; +"storage.measuringDiskUsage" = "Считаем место на диске… (du -sk для {prefix})"; +"storage.notPresent" = "нет на диске"; +"storage.revealInFinder" = "Показать {path} в Finder"; +"storage.showInFileManager" = "Показать {path} в файловом менеджере"; +"storage.revealLabelInFinder" = "Показать «{label}» в Finder"; +"storage.showLabelInFileManager" = "Показать «{label}» в файловом менеджере"; +"storage.pathMissing" = "Путь не существует"; +"resize.panel" = "Изменить размер панели"; +"resize.dragHint" = "Потяните, чтобы изменить размер; двойной щелчок сбрасывает ширину"; +"theme.cycle" = "Переключить тему"; +"theme.label" = "Тема"; +"trending.fetchHistory" = "Загружать историю трендов"; +"trending.fetchHistory.help" = "Загружает историю установок по пакетам для мини-графиков и графиков в карточке пакета."; +"trending.sparklineAria" = "График динамики установок"; +"trending.error.backend" = "Бэкенд недоступен: {error}"; +"trending.error.failed" = "Не удалось загрузить тренды: {error}"; +"trending.error.network" = "Не удалось подключиться к formulae.brew.sh"; +"updates.autoCheck" = "Проверять ежедневно"; +"updates.autoCheck.help" = "Автоматически проверять обновления brew-browser раз в день."; +"updates.available.help" = "Нажмите, чтобы открыть обновление."; +"updates.channel" = "Канал"; +"updates.channel.label" = "Канал обновлений"; +"updates.channel.stable" = "Стабильный"; +"updates.check.help" = "Проверяет ленту Sparkle на наличие новой сборки brew-browser."; +"updates.checkForUpdates" = "Проверить обновления…"; +"updates.checkNow" = "Проверить сейчас"; +"updates.lastChecked.format" = "Последняя проверка: %@"; +"updates.never" = "Никогда"; +"updates.noBeta" = "В этом релизе нет бета-канала."; +"updates.offlineBlocked" = "Офлайн-режим блокирует проверку обновлений."; +"upgrade.choosePackages" = "Выберите пакеты для обновления"; +"upgrade.noOutdated" = "Устаревших пакетов нет."; +"upgrade.pinned.help" = "Закреплённые пакеты не обновляются через brew. Снимите закрепление в карточке пакета."; +"deprecated" = "устарело"; +"discover.activeFilters" = "Активные фильтры категорий"; +"discover.catalogFetched" = "Загружено с formulae.brew.sh"; +"discover.catalogRefreshed" = "Каталог обновлён"; +"discover.categoryBrowsingHidden" = "Обзор по категориям скрыт, потому что функции ИИ выключены в «Настройки → Оформление»."; +"discover.categoryTileAria" = "{category} — {count} пакетов"; +"discover.deprecatedDefault" = "Устарело — может быть удалено при одном из будущих обновлений Homebrew."; +"discover.deprecatedReason" = "Устарело: {reason}"; +"discover.disabledDefault" = "Отключено — больше недоступно через Homebrew."; +"discover.disabledReason" = "Отключено: {reason}"; +"discover.emptyFilter" = "Под этот фильтр пакеты не подходят."; +"discover.groupPackagesLabel" = "Пакеты в группе «{group}»"; +"discover.loadingCategories" = "Загружаем категории…"; +"discover.noResults" = "По запросу «{query}» ничего не найдено."; +"discover.noResults.body" = "Попробуйте более короткий или другой запрос."; +"discover.noResultsInCategories" = "По запросу «{query}» нет результатов в выбранных категориях."; +"discover.noResultsInCategories.body" = "Уберите фильтр категории или расширьте запрос."; +"discover.packagesInCategory" = "Пакеты в категории «{category}»"; +"discover.removeFilter" = "Убрать фильтр «{category}»"; +"discover.searchAria" = "Поиск по Homebrew"; +"discover.searchFailed" = "Поиск не удался"; +"discover.searchPlaceholder" = "Поиск в индексе Homebrew…"; +"discover.searchPromptTitle" = "Введите запрос для поиска в индексе Homebrew"; +"discover.staleDismissAria" = "Скрыть предупреждение об устаревшем каталоге"; +"discover.subgroupNote" = "сгруппировано по пересекающимся категориям"; +"discover.subgroupsBody" = "У Homebrew нет официальных подкатегорий, поэтому это группировка по совместной встречаемости: внутри «{category}» пакеты раскладываются по другим категориям, к которым они тоже относятся. Пакет с несколькими пересекающимися категориями попадает в каждую такую группу. «Общее: {category}» содержит пакеты, отмеченные только категорией «{category}». Это подсказка для навигации, а не строгая таксономия."; +"discover.subgroupsLabel" = "О подкатегориях"; +"discover.subgroupsTitle" = "Как устроены эти подгруппы"; +"package.installTrendFor" = "Динамика установок {name}"; +"package.openToken" = "Открыть {token}"; +"recovery.reason.alreadyInstalled" = "{name} уже установлен вне Homebrew."; +"recovery.reason.stillRequired" = "{name} ещё нужен другому установленному пакету."; +"vulnerabilities.scanFailed" = "Не удалось выполнить проверку уязвимостей"; +"vulnerabilities.scanOneFailed" = "Не удалось проверить уязвимости для {name}"; +"settings.github.corruptSettings" = "Файл настроек не читается — откройте раздел «Сеть», чтобы сбросить настройки."; +"settings.github.githubUserFallback" = "пользователь GitHub"; +"settings.github.privacy.afterStruct" = "."; +"settings.github.privacy.beforeStruct" = "brew-browser хранит токен в {keyringName}. Токен никогда не отправляется в renderer-процесс по IPC, никогда не записывается на диск и никогда не попадает в логи. Через IPC передаётся только производное представление "; +"settings.github.scopes.afterNotifications" = " для отслеживания. Доступа к приватным репозиториям, email и администрированию нет."; +"settings.github.scopes.afterPublicRepo" = " для звёзд и создания issue, а также "; +"settings.github.scopes.afterReadUser" = " для отображения имени пользователя, "; +"settings.github.scopes.beforeReadUser" = "Вход необязателен. Запрашиваются минимальные разрешения: "; +"settings.github.signInHint" = "Открывает стандартный поток GitHub «Authorize app» в браузере. Пароль никогда не вводится в brew-browser. Вход нужен, чтобы ставить звёзды, отслеживать репозитории и создавать issue в GitHub из brew-browser."; +"settings.github.signInPurpose" = "Для чего используется вход"; +"settings.github.signedInAs" = "Выполнен вход как"; +"settings.github.statsHint.afterHost" = "."; +"settings.github.statsHint.beforeHost" = "Показывает звёзды, форки и последний релиз для пакетов, у которых домашняя страница ведёт на GitHub. brew-browser загружает публичные метаданные репозитория из "; +"settings.github.scopesLabel" = "Разрешения"; diff --git a/native/Sources/BrewBrowserKit/AboutView.swift b/native/Sources/BrewBrowserKit/AboutView.swift index 7a0e686..6aea625 100644 --- a/native/Sources/BrewBrowserKit/AboutView.swift +++ b/native/Sources/BrewBrowserKit/AboutView.swift @@ -38,16 +38,16 @@ struct AboutView: View { Text("🍺").font(.system(size: 48)) } Text("Brew Browser").font(.title.weight(.semibold)) - Text("A native macOS GUI for Homebrew.") + Text(L10n.string("about.tagline")) .font(.callout).foregroundStyle(.secondary) } // Meta card VStack(spacing: 8) { - metaRow("Version") { Text(appVersion).monospaced() } + metaRow(L10n.string("Version")) { Text(appVersion).monospaced() } metaRow("Homebrew") { Text(model.brewVersion).monospaced() } - metaRow("License") { Link("MIT", destination: Self.licenseURL) } - metaRow("Repo") { + metaRow(L10n.string("License")) { Link("MIT", destination: Self.licenseURL) } + metaRow(L10n.string("Repo")) { Link("github.com/msitarzewski/brew-browser", destination: Self.repoURL) .lineLimit(1).truncationMode(.middle) } @@ -61,7 +61,7 @@ struct AboutView: View { Link(destination: Self.sponsorURL) { Label { HStack(spacing: 4) { - Text("Donate to the project") + Text(L10n.string("Donate to the project")) Image(systemName: "arrow.up.right.square").font(.caption2) } } icon: { @@ -72,23 +72,23 @@ struct AboutView: View { } .buttonStyle(.borderedProminent) .tint(.orange) - .help("Open GitHub Sponsors in your browser") + .help(L10n.string("Open GitHub Sponsors in your browser")) // Built with VStack(alignment: .leading, spacing: 8) { - Label("Built with", systemImage: "sparkles") + Label(L10n.string("Built with"), systemImage: "sparkles") .font(.caption.weight(.semibold)) .foregroundStyle(.secondary) .textCase(.uppercase) - Text("[Agency Agents](\(Self.agencyURL.absoluteString)) — the multi-agent toolkit that orchestrated the waves (Backend Architect, Frontend Developer, Security Engineer, Code Reviewer, Technical Writer, and friends), powered by **Claude Code** in the terminal, running **Opus 4.7 [1m]**.") + Text("[Agency Agents](\(Self.agencyURL.absoluteString))\(L10n.string("about.creditsBody.beforeClaude"))**Claude Code**\(L10n.string("about.creditsBody.afterClaude"))**Opus 4.7 [1m]**.") .font(.callout).foregroundStyle(.secondary) - Text("Thanks also to the Homebrew project for the package data, every formula and cask maintainer for their work, and Tauri for the native shell.") + Text(L10n.string("about.thanksMac")) .font(.callout).italic().foregroundStyle(.secondary).opacity(0.85) } .frame(maxWidth: .infinity, alignment: .leading) // Posture - Text("Zero telemetry. Zero accounts. Every outbound network call is documented in Settings → Network.") + Text(L10n.string("about.posture")) .font(.caption).foregroundStyle(.secondary) .multilineTextAlignment(.center) } @@ -99,7 +99,7 @@ struct AboutView: View { .buttonStyle(.plain) .foregroundStyle(.secondary) .padding(12) - .help("Close") + .help(L10n.string("Close")) } .onKeyPress(.escape) { dismiss(); return .handled } } diff --git a/native/Sources/BrewBrowserKit/ActivityView.swift b/native/Sources/BrewBrowserKit/ActivityView.swift index bda194e..2939c32 100644 --- a/native/Sources/BrewBrowserKit/ActivityView.swift +++ b/native/Sources/BrewBrowserKit/ActivityView.swift @@ -20,15 +20,7 @@ struct ActivityDrawer: View { /// form ("Installing X"); on completion show the terminal form so a green ✓ /// doesn't sit next to "Installing". static func displayLabel(_ job: ActivityJob) -> String { - switch job.status { - case .running: return job.label - case .succeeded: return job.label - .replacingOccurrences(of: "Installing ", with: "Installed ") - .replacingOccurrences(of: "Upgrading ", with: "Upgraded ") - .replacingOccurrences(of: "Uninstalling ", with: "Uninstalled ") - case .failed: return "Failed: \(job.label)" - case .canceled: return "Canceled: \(job.label)" - } + L10n.activityDisplayLabel(job) } var body: some View { @@ -75,21 +67,21 @@ struct ActivityDrawer: View { if job.status == .running { Button { model.cancelJob(job.id) - } label: { Label("Cancel", systemImage: "stop.circle") } + } label: { Label(L10n.string("action.cancel"), systemImage: "stop.circle") } .buttonStyle(.borderless) } Button { copyLog(job) } label: { Image(systemName: "doc.on.doc") } .buttonStyle(.borderless) - .help("Copy output") + .help(L10n.string("activity.copyOutput")) Button { model.drawerOpen.toggle() } label: { Image(systemName: model.drawerOpen ? "chevron.down" : "chevron.up") } .buttonStyle(.borderless) - .help(model.drawerOpen ? "Collapse" : "Expand") + .help(model.drawerOpen ? L10n.string("activity.collapse") : L10n.string("activity.expand")) // Close — dismiss the drawer for this job (stays in Activity history). // Cancels first if it's still running. Mirrors the Tauri drawer's X. Button { @@ -99,7 +91,7 @@ struct ActivityDrawer: View { Image(systemName: "xmark") } .buttonStyle(.borderless) - .help("Close") + .help(L10n.string("action.close")) } .padding(.horizontal, 12) .padding(.vertical, 8) @@ -132,10 +124,7 @@ struct ActivityDrawer: View { } static func progressLabel(_ p: JobProgress) -> String { - var s = p.phase - if !p.package.isEmpty { s += " \(p.package)" } - if let total = p.total { s += " (\(p.current) of \(total))" } - return s + L10n.progressLabel(p) } @ViewBuilder @@ -195,18 +184,20 @@ struct ActivityDrawer: View { } else if !AppModel.isAppError(job) { // brew ran and exited non-zero: a Homebrew/formula problem, // NOT a brew-browser bug. No report — point at Terminal. - Text("This looks like a Homebrew error, not a brew-browser bug — the command ran but exited with status \(job.exitCode ?? 0). Try the same command in Terminal; if it fails there too, it's a Homebrew or formula issue to report upstream.") + Text(L10n.isRussian + ? "Похоже, это ошибка Homebrew, а не brew-browser: команда выполнилась и завершилась с кодом \(job.exitCode ?? 0). Попробуйте ту же команду в Терминале; если она тоже завершается ошибкой, сообщите о проблеме в Homebrew или мейнтейнерам соответствующей формулы или cask-пакета." + : "This looks like a Homebrew error, not a brew-browser bug — the command ran but exited with status \(job.exitCode ?? 0). Try the same command in Terminal; if it fails there too, it's a Homebrew or formula issue to report upstream.") .font(.caption) .foregroundStyle(.secondary) .textSelection(.enabled) .fixedSize(horizontal: false, vertical: true) } else { // No brew exit code = the app couldn't run the command. Our bug. - Text("brew-browser hit an unexpected error running this command.") + Text(L10n.string("brew-browser hit an unexpected error running this command.")) .font(.caption) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) - Button("Report to brew-browser") { + Button(L10n.string("Report to brew-browser")) { ReportIssue.open(for: job, brewVersion: model.brewVersion) } .buttonStyle(.link) @@ -262,7 +253,7 @@ struct ActivityDrawer: View { .foregroundStyle(Self.footerColor(job.status)) .lineLimit(3) if job.status == .failed && AppModel.isAppError(job) { - Button("Report to brew-browser") { + Button(L10n.string("Report to brew-browser")) { ReportIssue.open(for: job, brewVersion: model.brewVersion) } .buttonStyle(.link) @@ -302,22 +293,32 @@ struct ActivityDrawer: View { /// Compact duration like "4.2s" from durationMs. static func duration(_ ms: Int?) -> String? { guard let ms else { return nil } - return String(format: "%.1fs", Double(ms) / 1000) + let seconds = Double(ms) / 1000 + if L10n.isRussian { + return String(format: "%.1f с", seconds).replacingOccurrences(of: ".", with: ",") + } + return String(format: "%.1fs", seconds) } static func footerText(_ job: ActivityJob) -> String { let dur = duration(job.durationMs) switch job.status { case .running: return "" - case .succeeded: return dur.map { "Done in \($0)." } ?? "Done." + case .succeeded: + if L10n.isRussian { return dur.map { "Готово за \($0)." } ?? "Готово." } + return dur.map { "Done in \($0)." } ?? "Done." case .failed: - let base = dur.map { "Failed after \($0)." } ?? "Failed." + let base = L10n.isRussian + ? (dur.map { "Ошибка через \($0)." } ?? "Ошибка.") + : (dur.map { "Failed after \($0)." } ?? "Failed.") if let friendly = job.friendlyFailureMessage, !friendly.isEmpty { - return "\(base) See notice at right." + return L10n.isRussian ? "\(base) См. пояснение справа." : "\(base) See notice at right." + } + if let code = job.exitCode, code != 0 { + return L10n.isRussian ? "\(base) Код выхода \(code)." : "\(base) Exit \(code)." } - if let code = job.exitCode, code != 0 { return "\(base) Exit \(code)." } return base - case .canceled: return "Stopped." + case .canceled: return L10n.isRussian ? "Остановлено." : "Stopped." } } @@ -368,9 +369,9 @@ struct ActivityView: View { Group { if model.jobs.isEmpty { ContentUnavailableView( - "No activity yet", + L10n.string("No activity yet"), systemImage: "list.bullet.rectangle", - description: Text("Installs, upgrades, uninstalls, and updates show up here.") + description: Text(L10n.string("Installs, upgrades, uninstalls, and updates show up here.")) ) } else { List(model.jobs) { job in @@ -382,7 +383,7 @@ struct ActivityView: View { HStack(spacing: 10) { icon(job.status) VStack(alignment: .leading, spacing: 2) { - Text(job.label).fontWeight(.medium) + Text(ActivityDrawer.displayLabel(job)).fontWeight(.medium) Text(job.command).font(.caption.monospaced()).foregroundStyle(.secondary) } Spacer() @@ -410,7 +411,7 @@ struct ActivityView: View { .frame(width: 24) .opacity(hovered == job.id ? 1 : 0) .allowsHitTesting(hovered == job.id) - .help("Remove from history") + .help(L10n.string("Remove from history")) } .onHover { inside in if inside { hovered = job.id } @@ -418,12 +419,12 @@ struct ActivityView: View { } .swipeActions(edge: .trailing) { Button(role: .destructive) { model.removeJob(job.id) } label: { - Label("Remove", systemImage: "trash") + Label(L10n.string("Remove"), systemImage: "trash") } } .contextMenu { Button(role: .destructive) { model.removeJob(job.id) } label: { - Label("Remove", systemImage: "trash") + Label(L10n.string("Remove"), systemImage: "trash") } } } @@ -432,7 +433,7 @@ struct ActivityView: View { .toolbar { if model.jobs.contains(where: { $0.status != .running }) { ToolbarItem(placement: .primaryAction) { - Button("Clear Finished") { model.clearFinishedJobs() } + Button(L10n.string("Clear Finished")) { model.clearFinishedJobs() } } } } @@ -441,7 +442,15 @@ struct ActivityView: View { /// Right-aligned status string; failures append their exit code. private func statusText(_ job: ActivityJob) -> String { if job.status == .failed, let code = job.exitCode, code != 0 { - return "Failed · exit \(code)" + return L10n.isRussian ? "Ошибка · код \(code)" : "Failed · exit \(code)" + } + if L10n.isRussian { + switch job.status { + case .running: return "Выполняется" + case .succeeded: return "Готово" + case .failed: return "Ошибка" + case .canceled: return "Отменено" + } } return job.status.rawValue.capitalized } diff --git a/native/Sources/BrewBrowserKit/AppModel.swift b/native/Sources/BrewBrowserKit/AppModel.swift index 64e496c..a910261 100644 --- a/native/Sources/BrewBrowserKit/AppModel.swift +++ b/native/Sources/BrewBrowserKit/AppModel.swift @@ -14,6 +14,18 @@ enum Section: String, CaseIterable, Identifiable, Hashable { var id: String { rawValue } + var localizedTitle: String { + switch self { + case .dashboard: return L10n.string("nav.dashboard") + case .library: return L10n.string("nav.library") + case .discover: return L10n.string("nav.discover") + case .trending: return L10n.string("nav.trending") + case .snapshots: return L10n.string("nav.snapshots") + case .services: return L10n.string("nav.services") + case .activity: return L10n.string("nav.activity") + } + } + /// SF Symbol for the sidebar row — all system symbols, no custom assets. var symbol: String { switch self { @@ -43,6 +55,18 @@ enum LibraryFilter: String, CaseIterable, Identifiable, Hashable { case vulnerable = "Vulnerable" var id: String { rawValue } + + var localizedTitle: String { + switch self { + case .all: return L10n.string("filter.all") + case .formulae: return L10n.string("package.kind.formulae") + case .casks: return L10n.string("package.kind.casks") + case .outdated: return L10n.string("filter.outdated") + case .manual: return L10n.string("filter.manual") + case .dependency: return L10n.string("filter.dependency") + case .vulnerable: return L10n.string("filter.vulnerable") + } + } } /// A flattened Library table row. Built in `AppModel.libraryRows` so the @@ -318,9 +342,7 @@ public final class AppModel { /// flash empty. var catalogDaysOldLabel: String { guard let s = catalogSummary else { return "—" } - if s.daysOld <= 0 { return "today" } - if s.daysOld == 1 { return "1 day old" } - return "\(s.daysOld) days old" + return L10n.catalogAge(days: s.daysOld) } /// Discover rows: catalog filtered by category + the shared search field, @@ -346,9 +368,9 @@ public final class AppModel { slug: cat, subgroupKey: subKey) ?? false) { return nil } + let searchEntry = enrichmentEntry(for: pkg.token) if !q.isEmpty, - !pkg.token.localizedCaseInsensitiveContains(q), - !pkg.displayName.localizedCaseInsensitiveContains(q) { + !catalogPackageMatches(pkg, query: q, entry: searchEntry) { return nil } let entry = showSummary ? enrichmentEntry(for: pkg.token) : nil @@ -381,7 +403,7 @@ public final class AppModel { if bundledDataLoaded { return } bundledDataLoaded = true let (cat, enr) = await Task.detached(priority: .userInitiated) { - (CategoryCatalog.loadBundled(), EnrichmentCatalog.loadBundled()) + (CategoryCatalog.loadBundled(), EnrichmentCatalog.loadBundled(locale: L10n.activeLanguage)) }.value categoryCatalog = cat enrichment = enr @@ -427,7 +449,7 @@ public final class AppModel { func refreshCatalogFromBrewSh() async { guard !catalogRefreshing else { return } guard settings.networkAllowed("catalog_refresh") else { - catalogRefreshError = "Offline mode is on — catalog refresh is blocked. Disable it in Settings → Network." + catalogRefreshError = L10n.string("catalog.refresh.offlineBlocked") return } catalogRefreshing = true @@ -776,9 +798,7 @@ public final class AppModel { return Array( catalog .lazy - .filter { - $0.token.lowercased().contains(q) || $0.displayName.lowercased().contains(q) - } + .filter { self.catalogPackageMatches($0, query: q, entry: self.enrichmentEntry(for: $0.token)) } .filter { !installedNames.contains($0.token.lowercased()) } .prefix(10) ) @@ -1192,7 +1212,7 @@ public final class AppModel { /// the Tauri `env.shortLabel` (`env.svelte.ts:37-42`). var brewShortLabel: String { switch brewHealth { - case .missing: return "brew not found" + case .missing: return L10n.string("brew.status.notFound") case .unknown: return "brew" default: return brewVersion == "—" ? "brew" : "brew \(brewVersion)" } @@ -1203,13 +1223,13 @@ public final class AppModel { var brewStatusTooltip: String { var base: String switch brewHealth { - case .unknown: base = "Checking Homebrew…" - case .missing: base = "Homebrew not found on PATH." - default: base = "Homebrew \(brewVersion) · prefix \(brewPrefix)" + case .unknown: base = L10n.string("brew.status.checking") + case .missing: base = L10n.string("brew.status.notFoundOnPath") + default: base = String(format: L10n.string("brew.status.ready.format"), brewVersion, brewPrefix) } let running = jobs.filter { $0.status == .running }.count if running > 0 { - base += "\n\(running) brew operation\(running == 1 ? "" : "s") running" + base += "\n\(L10n.brewOperationsRunning(running))" } return base } @@ -1484,15 +1504,59 @@ public final class AppModel { // MARK: - Live enrichment overlay (opt-in) - /// Enrichment for a token, with the live overlay preferred over the bundled - /// catalog (mirrors the Tauri enrichment store's `lookup`). + /// Enrichment for a token. In English, live enrichment wins over bundled + /// data. In localized UI, localized bundled prose wins while live data can + /// still fill technical fallback fields (mirrors the Tauri store). func enrichmentEntry(for token: String) -> EnrichmentEntry? { - if let hit = liveEnrichment[token] ?? enrichment?.entry(for: token) { return hit } + if let hit = preferredEnrichment( + live: liveEnrichment[token], + bundled: enrichment?.entry(for: token) + ) { return hit } // Tap-qualified token (`user/tap/name`) → retry under the bare name the // enrichment is keyed by. let bare = Self.bareToken(token) guard bare != token else { return nil } - return liveEnrichment[bare] ?? enrichment?.entry(for: bare) + return preferredEnrichment( + live: liveEnrichment[bare], + bundled: enrichment?.entry(for: bare) + ) + } + + private func preferredEnrichment( + live: EnrichmentEntry?, + bundled: EnrichmentEntry? + ) -> EnrichmentEntry? { + guard L10n.isRussian, let bundled else { return live ?? bundled } + guard let live else { return bundled } + return bundled.localized(over: live) + } + + private func catalogPackageMatches( + _ pkg: CatalogPackage, + query: String, + entry: EnrichmentEntry? + ) -> Bool { + let fields: [String?] = [ + pkg.token, + pkg.displayName, + pkg.desc, + entry?.friendlyName, + entry?.summary, + ] + if fields.contains(where: { $0?.localizedCaseInsensitiveContains(query) == true }) { + return true + } + if entry?.searchTerms.contains(where: { $0.localizedCaseInsensitiveContains(query) }) == true { + return true + } + if entry?.tags.contains(where: { $0.localizedCaseInsensitiveContains(query) }) == true { + return true + } + let labels = categoryCatalog?.categoryLabels(for: pkg.token, kind: pkg.kind) ?? [] + return labels.contains { + $0.localizedCaseInsensitiveContains(query) + || L10n.display($0).localizedCaseInsensitiveContains(query) + } } /// Fetch a token's live enrichment on demand and overlay it. Deduped + @@ -1606,9 +1670,9 @@ public final class AppModel { /// the Tauri scope-required toast (`PackageDetail.svelte:528-540`). private func requireScope(_ scope: String, action: String) -> Bool { if githubStatus?.scopes.contains(scope) ?? false { return true } - pushToast(.error, "\(action) needs more access", - "Needs the \"\(scope)\" GitHub permission. Click to grant it without signing out.", - action: ToastAction(label: "Re-authorize") { [weak self] in + pushToast(.error, String(format: L10n.string("github.scopeRequired.title.format"), L10n.githubAction(action)), + String(format: L10n.string("github.scopeRequired.body.format"), scope), + action: ToastAction(label: L10n.string("Re-authorize")) { [weak self] in Task { await self?.reauthorizeGitHub() } }) return false @@ -2044,7 +2108,7 @@ public final class AppModel { private func recordFailedServiceJob(_ verb: ServiceVerb, name: String, error: Error) { let job = ActivityJob( id: UUID(), - label: "\(verb.verbLabel) \(name) failed", + label: "\(verb.verbLabel) \(name)", command: "brew services \(verb.rawValue) \(name)", startedAt: Date().timeIntervalSince1970, status: .failed, @@ -2164,10 +2228,7 @@ public final class AppModel { } static func failureNoticeTitle(for label: String) -> String { - if label.hasPrefix("Upgrading ") { return "Upgrade failed" } - if label.hasPrefix("Installing ") { return "Install failed" } - if label.hasPrefix("Uninstalling ") { return "Uninstall failed" } - return "\(label) failed" + L10n.activityFailureTitle(for: label) } /// True when a failed job is a genuine brew-browser problem (worth a diff --git a/native/Sources/BrewBrowserKit/BrewOutputParsing.swift b/native/Sources/BrewBrowserKit/BrewOutputParsing.swift index afb75a0..5516f49 100644 --- a/native/Sources/BrewBrowserKit/BrewOutputParsing.swift +++ b/native/Sources/BrewBrowserKit/BrewOutputParsing.swift @@ -17,6 +17,9 @@ enum BrewErrorPatterns { if isBundle, stderr.contains("key not found:"), stderr.contains("Homebrew::Bundle::Brew::Topo") { + if L10n.isRussian { + return "В `brew bundle` сработала внутренняя ошибка topo-sort для одной из установленных формул. Это ошибка Homebrew, а не brew-browser. Попробуйте `brew untap` для недавно добавленного tap или посмотрите полный вывод в Активности." + } return "Homebrew's `brew bundle` hit an internal topo-sort error on one " + "of your installed formulae. This is an upstream Homebrew bug, not " + "a brew-browser issue. Try `brew untap` on a recently-added tap, or " @@ -26,6 +29,9 @@ enum BrewErrorPatterns { // Pattern 2 — Homebrew explicitly asks the user to report upstream. if stderr.contains("Please report this issue:"), stderr.contains("docs.brew.sh/Troubleshooting") { + if L10n.isRussian { + return "Homebrew сообщил о внутренней ошибке и попросил отправить отчёт upstream. Посмотрите полный вывод в Активности и откройте https://docs.brew.sh/Troubleshooting для дальнейших шагов." + } return "Homebrew reported an internal error and asked you to report it " + "upstream. See the full output in Activity, and visit " + "https://docs.brew.sh/Troubleshooting for next steps." @@ -35,6 +41,9 @@ enum BrewErrorPatterns { // current session doesn't own (ports PR #51). if command.contains("services"), stderr.contains("Could not find service") || stderr.contains("not found in domain") { + if L10n.isRussian { + return "brew не нашёл эту службу в текущем домене launchd. Скорее всего, plist зарегистрирован для другого пользователя или сеанса. Попробуйте `brew services restart` или перенесите plist в ~/Library/LaunchAgents и выполните `launchctl bootstrap gui/$UID`." + } return "brew could not find this service in the current launchd domain. " + "The plist is likely registered for a different user or session. " + "Try `brew services restart`, or move the plist to " @@ -44,6 +53,9 @@ enum BrewErrorPatterns { // Pattern 4 — another brew process holds the lock (environmental). if stderr.contains("has already locked") || stderr.contains("Please wait for it to finish or terminate it") { + if L10n.isRussian { + return "Другой процесс Homebrew уже выполняется и удерживает lock. Дождитесь его завершения или закройте другой процесс, затем повторите попытку — это не проблема brew-browser." + } return "Another Homebrew process is already running and holds the lock. " + "Wait for it to finish (or quit the other process) and try again — " + "this isn't a brew-browser problem." @@ -53,11 +65,17 @@ enum BrewErrorPatterns { // has no interactive terminal for the admin password prompt. if sudoPasswordPromptRequired(stderr) { if let token = sudoCaskToken(stderr: stderr, command: command) { + if L10n.isRussian { + return "Cask-пакету `\(token)` нужен пароль администратора, но brew-browser запускает brew без интерактивного терминала, поэтому sudo не может запросить пароль здесь. Выполните в Терминале: `brew upgrade --cask \(token)`, затем обновите brew-browser." + } return "The cask `\(token)` needs an administrator password, but " + "brew-browser runs brew without an interactive terminal so sudo " + "cannot prompt here. Run this in Terminal: " + "`brew upgrade --cask \(token)`, then refresh brew-browser." } + if L10n.isRussian { + return "Этому Homebrew cask нужен пароль администратора, но brew-browser запускает brew без интерактивного терминала, поэтому sudo не может запросить пароль здесь. Выполните обновление cask-пакета в Терминале, затем обновите brew-browser." + } return "This Homebrew cask needs an administrator password, but " + "brew-browser runs brew without an interactive terminal so sudo " + "cannot prompt here. Run the cask upgrade in Terminal, then " diff --git a/native/Sources/BrewBrowserKit/BrewRecovery.swift b/native/Sources/BrewBrowserKit/BrewRecovery.swift index e549eb3..71fc0a0 100644 --- a/native/Sources/BrewBrowserKit/BrewRecovery.swift +++ b/native/Sources/BrewBrowserKit/BrewRecovery.swift @@ -64,23 +64,23 @@ enum BrewRecovery { // --adopt only exists for casks; it's the safe, recommended fix. if kind == .cask { choices.append(ChoiceSpec( - choice: .adopt, label: "Adopt existing", isDanger: false, - hint: "Let Homebrew manage the copy already on your Mac (keeps it in place).")) + choice: .adopt, label: L10n.string("Adopt existing"), isDanger: false, + hint: L10n.isRussian ? "Передать Homebrew уже установленную копию на этом Mac, не перемещая её." : "Let Homebrew manage the copy already on your Mac (keeps it in place).")) } choices.append(ChoiceSpec( - choice: .overwrite, label: "Overwrite", isDanger: true, - hint: "Replace the existing copy with a fresh Homebrew install.")) + choice: .overwrite, label: L10n.isRussian ? "Заменить" : "Overwrite", isDanger: true, + hint: L10n.string("Replace the existing copy with a fresh Homebrew install."))) return Option(action: action, name: name, kind: kind, - reason: "\(name) is already installed outside Homebrew.", + reason: L10n.isRussian ? "\(name) уже установлен вне Homebrew." : "\(name) is already installed outside Homebrew.", choices: choices) } if action == .uninstall, requiredBy(text) { return Option(action: action, name: name, kind: kind, - reason: "\(name) is still required by another installed package.", + reason: L10n.isRussian ? "\(name) всё ещё требуется другому установленному пакету." : "\(name) is still required by another installed package.", choices: [ChoiceSpec( - choice: .forceRemove, label: "Force remove", isDanger: true, - hint: "Remove it anyway, ignoring packages that depend on it.")]) + choice: .forceRemove, label: L10n.string("Force remove"), isDanger: true, + hint: L10n.string("Remove it anyway, ignoring packages that depend on it."))]) } return nil diff --git a/native/Sources/BrewBrowserKit/BrewService.swift b/native/Sources/BrewBrowserKit/BrewService.swift index 9b5eaa5..9f630dd 100644 --- a/native/Sources/BrewBrowserKit/BrewService.swift +++ b/native/Sources/BrewBrowserKit/BrewService.swift @@ -93,8 +93,31 @@ enum BrewError: Error, LocalizedError { var errorDescription: String? { switch self { case .brewNotFound: + if L10n.isRussian { + return "Не удалось найти исполняемый файл brew. Установлен ли Homebrew?" + } return "Couldn't find the brew executable. Is Homebrew installed?" case let .nonZeroExit(code, stderr): + if L10n.isRussian { + if stderr == "Unparseable brew info --installed JSON" { + return "Не удалось разобрать ответ brew info --installed." + } + if stderr == "Unparseable brew services JSON" { + return "Не удалось разобрать ответ brew services." + } + if stderr == "Unparseable brew info JSON" { + return "Не удалось разобрать ответ brew info." + } + if stderr.hasPrefix("No formulae entry for ") { + let name = String(stderr.dropFirst("No formulae entry for ".count)) + return "В ответе brew info нет записи для \(name)." + } + if stderr.hasPrefix("No casks entry for ") { + let name = String(stderr.dropFirst("No casks entry for ".count)) + return "В ответе brew info нет записи для \(name)." + } + return "brew завершился с кодом \(code): \(stderr)" + } return "brew exited with code \(code): \(stderr)" } } diff --git a/native/Sources/BrewBrowserKit/CatalogService.swift b/native/Sources/BrewBrowserKit/CatalogService.swift index b9a77a6..88c07f0 100644 --- a/native/Sources/BrewBrowserKit/CatalogService.swift +++ b/native/Sources/BrewBrowserKit/CatalogService.swift @@ -471,12 +471,12 @@ actor CatalogService { var errorDescription: String? { switch self { - case .noWritableDir: return "Couldn't locate a writable catalog directory." - case .network(let m): return "Catalog refresh failed: \(m)" - case .malformedResponse: return "brew.sh returned an unexpected catalog format." + case .noWritableDir: return L10n.string("catalog.refresh.noWritableDir") + case .network(let m): return String(format: L10n.string("catalog.refresh.failed.format"), m) + case .malformedResponse: return L10n.string("catalog.refresh.malformedResponse") case .emptyResponse(let f, let c): - return "brew.sh returned an empty catalog (formulae=\(f), casks=\(c))." - case .compression: return "Couldn't compress the refreshed catalog." + return String(format: L10n.string("catalog.refresh.emptyResponse.format"), f, c) + case .compression: return L10n.string("catalog.refresh.compression") } } } diff --git a/native/Sources/BrewBrowserKit/CommandPaletteView.swift b/native/Sources/BrewBrowserKit/CommandPaletteView.swift index 9a0742d..7627fbd 100644 --- a/native/Sources/BrewBrowserKit/CommandPaletteView.swift +++ b/native/Sources/BrewBrowserKit/CommandPaletteView.swift @@ -49,15 +49,15 @@ struct CommandPaletteView: View { private var commands: [Item] { [ - .command(id: "dashboard", label: "Open Dashboard", shortcut: "⌘0") { model.go(toSectionNumber: 0) }, - .command(id: "library", label: "Open Library", shortcut: "⌘1") { model.go(toSectionNumber: 1) }, - .command(id: "discover", label: "Open Discover", shortcut: "⌘2") { model.go(toSectionNumber: 2) }, - .command(id: "trending", label: "Open Trending", shortcut: "⌘3") { model.go(toSectionNumber: 3) }, - .command(id: "snapshots", label: "Open Snapshots", shortcut: "⌘4") { model.go(toSectionNumber: 4) }, - .command(id: "services", label: "Open Services", shortcut: "⌘5") { model.go(toSectionNumber: 5) }, - .command(id: "activity", label: "Open Activity", shortcut: "⌘6") { model.go(toSectionNumber: 6) }, - .command(id: "drawer", label: "Toggle Activity Drawer", shortcut: "⌘L") { model.toggleDrawer() }, - .command(id: "refresh", label: "Refresh", shortcut: "⌘R") { Task { await model.refreshCurrent() } }, + .command(id: "dashboard", label: L10n.string("Open Dashboard"), shortcut: "⌘0") { model.go(toSectionNumber: 0) }, + .command(id: "library", label: L10n.string("Open Library"), shortcut: "⌘1") { model.go(toSectionNumber: 1) }, + .command(id: "discover", label: L10n.string("Open Discover"), shortcut: "⌘2") { model.go(toSectionNumber: 2) }, + .command(id: "trending", label: L10n.string("Open Trending"), shortcut: "⌘3") { model.go(toSectionNumber: 3) }, + .command(id: "snapshots", label: L10n.string("Open Snapshots"), shortcut: "⌘4") { model.go(toSectionNumber: 4) }, + .command(id: "services", label: L10n.string("Open Services"), shortcut: "⌘5") { model.go(toSectionNumber: 5) }, + .command(id: "activity", label: L10n.string("Open Activity"), shortcut: "⌘6") { model.go(toSectionNumber: 6) }, + .command(id: "drawer", label: L10n.string("Toggle Activity drawer"), shortcut: "⌘L") { model.toggleDrawer() }, + .command(id: "refresh", label: L10n.string("Refresh"), shortcut: "⌘R") { Task { await model.refreshCurrent() } }, ] } @@ -66,14 +66,14 @@ struct CommandPaletteView: View { let installed = model.paletteInstalled(query) if !installed.isEmpty { - out.append(Group(label: "Installed", items: installed.map { + out.append(Group(label: L10n.string("Installed"), items: installed.map { .package(token: $0.name, kind: $0.kind, homepage: "", installed: true) })) } let index = model.paletteCatalog(query) if !index.isEmpty { - out.append(Group(label: "Index", items: index.map { + out.append(Group(label: L10n.string("Index"), items: index.map { .package(token: $0.token, kind: $0.kind, homepage: $0.homepage, installed: false) })) } @@ -86,7 +86,7 @@ struct CommandPaletteView: View { return false } if !cmds.isEmpty { - out.append(Group(label: "Commands", items: cmds)) + out.append(Group(label: L10n.string("Commands"), items: cmds)) } return out } @@ -111,7 +111,7 @@ struct CommandPaletteView: View { private var searchField: some View { HStack(spacing: 8) { Image(systemName: "magnifyingglass").foregroundStyle(.secondary) - TextField("Type a command, package, or section.", text: $query) + TextField(L10n.string("Type a command, package, or section."), text: $query) .textFieldStyle(.plain) .font(.title3) .focused($searchFocused) @@ -135,7 +135,7 @@ struct CommandPaletteView: View { @ViewBuilder private var results: some View { if flatItems.isEmpty { - ContentUnavailableView("No results", systemImage: "magnifyingglass") + ContentUnavailableView(L10n.string("No results"), systemImage: "magnifyingglass") .frame(maxWidth: .infinity, maxHeight: .infinity) } else { ScrollViewReader { proxy in @@ -174,7 +174,7 @@ struct CommandPaletteView: View { PackageIcon(model: model, token: token, kind: kind, homepage: homepage, size: 18) Text(token) Spacer() - Text(installed ? "\(kind.rawValue) · installed" : kind.rawValue) + Text(installed ? "\(L10n.packageKind(kind)) · \(L10n.display("installed"))" : L10n.packageKind(kind)) .font(.callout).foregroundStyle(.secondary) } case .command(_, let label, let shortcut, _): @@ -191,9 +191,9 @@ struct CommandPaletteView: View { private var footer: some View { HStack(spacing: 14) { - legend("↑↓", "navigate") - legend("⏎", "open") - legend("esc", "close") + legend("↑↓", L10n.string("keyboard.navigate")) + legend("⏎", L10n.string("keyboard.open")) + legend("esc", L10n.string("keyboard.close")) Spacer() } .font(.caption) diff --git a/native/Sources/BrewBrowserKit/ContentView.swift b/native/Sources/BrewBrowserKit/ContentView.swift index 6e9a177..2e6def5 100644 --- a/native/Sources/BrewBrowserKit/ContentView.swift +++ b/native/Sources/BrewBrowserKit/ContentView.swift @@ -50,7 +50,7 @@ public struct ContentView: View { VStack(spacing: 0) { NavigationSplitView { List(Section.allCases, selection: $model.selection) { section in - Label(section.rawValue, systemImage: section.symbol) + Label(section.localizedTitle, systemImage: section.symbol) .badge(model.badge(for: section) ?? 0) // stock count badge; 0 hides it .tag(section) } @@ -90,7 +90,7 @@ public struct ContentView: View { // the window then grabs as a resize, hiding the inspector). // Pairs with .windowResizability(.contentMinSize) on the scene. .frame(minWidth: 420, maxWidth: .infinity, maxHeight: .infinity) - .navigationTitle(model.selection.rawValue) + .navigationTitle(model.selection.localizedTitle) .toolbar { ToolbarItemGroup(placement: .primaryAction) { // "Update available" pill (Bundle C) — mirrors the Tauri @@ -102,10 +102,10 @@ public struct ContentView: View { Button { updater.checkForUpdates() } label: { - Label("Update available", systemImage: "arrow.up.circle.fill") + Label(L10n.string("updates.available"), systemImage: "arrow.up.circle.fill") .foregroundStyle(.orange) } - .help("A newer version of brew-browser is available — click to update") + .help(L10n.string("updates.available.help")) } Button { @@ -115,7 +115,7 @@ public struct ContentView: View { // toolbar button shows active feedback, not just a // disabled grey-out. Label { - Text("Refresh") + Text(L10n.string("action.refresh")) } icon: { if model.isLoading { ProgressView().controlSize(.small) @@ -135,7 +135,7 @@ public struct ContentView: View { Image(systemName: "heart.fill") .foregroundStyle(.pink) } - .help("Support brew-browser via GitHub Sponsors") + .help(L10n.string("chrome.donate.help")) // GitHub connection chip — only when signed in. Green when // the public_repo scope is present (star/watch/issue work), @@ -150,13 +150,13 @@ public struct ContentView: View { .foregroundStyle(model.githubScopeComplete ? Color.green : Color.orange) } .help(model.githubScopeComplete - ? "GitHub: connected as @\(model.githubStatus?.username ?? "user")" - : "GitHub: signed in — scope incomplete; open Settings to fix") + ? String(format: L10n.string("github.connectedAs.format"), model.githubStatus?.username ?? "user") + : L10n.string("github.scopeIncomplete")) } // Stock SettingsLink opens the native Settings scene. SettingsLink { - Label("Settings", systemImage: "gearshape") + Label(L10n.string("settings.title"), systemImage: "gearshape") } } } @@ -165,7 +165,7 @@ public struct ContentView: View { // doesn't drift over the inspector (and the inspector's boundary // divider falls cleanly at the panel edge, not through the // toolbar between our icons and the field). - .searchable(text: $model.globalQuery, placement: .toolbar, prompt: "Search packages") + .searchable(text: $model.globalQuery, placement: .toolbar, prompt: Text(L10n.string("search.packages"))) .searchSuggestions { ForEach(model.suggestions) { pkg in HStack(spacing: 8) { @@ -283,11 +283,11 @@ struct LibraryView: View { var body: some View { Group { if model.isLoading && model.installed.isEmpty { - ProgressView("Reading your Homebrew install…") + ProgressView(L10n.string("Reading your Homebrew install…")) .frame(maxWidth: .infinity, maxHeight: .infinity) } else if let err = model.loadError { ContentUnavailableView( - "Couldn't load packages", + L10n.string("Couldn't load packages"), systemImage: "exclamationmark.triangle", description: Text(err) ) @@ -324,9 +324,9 @@ struct LibraryView: View { // Segmented type filter with per-filter counts. Stock control, no overrides. // Centered in the bar — the macOS view-switcher convention (Finder/Preview). private var filterBar: some View { - Picker("Filter", selection: $model.libraryFilter) { + Picker(L10n.string("Filter"), selection: $model.libraryFilter) { ForEach(model.availableLibraryFilters) { f in - Text("\(f.rawValue) (\(model.libraryFilterCount(f)))").tag(f) + Text("\(f.localizedTitle) (\(model.libraryFilterCount(f)))").tag(f) } } .pickerStyle(.segmented) @@ -346,7 +346,7 @@ struct LibraryView: View { model.clearLibraryCategory() } label: { HStack(spacing: 4) { - Text(label) + Text(L10n.display(label)) Image(systemName: "xmark.circle.fill") .foregroundStyle(.secondary) } @@ -356,7 +356,7 @@ struct LibraryView: View { .background(Color.secondary.opacity(0.12), in: .capsule) } .buttonStyle(.plain) - .help("Clear category filter") + .help(L10n.isRussian ? "Сбросить фильтр категорий" : "Clear category filter") Spacer() } .padding(.horizontal, 12) @@ -368,9 +368,11 @@ struct LibraryView: View { if model.sortedLibraryRows.isEmpty { if model.globalQuery.isEmpty { ContentUnavailableView( - "No packages", + L10n.string("No packages"), systemImage: "shippingbox", - description: Text("Nothing matches the \(model.libraryFilter.rawValue.lowercased()) filter.") + description: Text(L10n.isRussian + ? "По фильтру «\(model.libraryFilter.localizedTitle)» ничего не найдено." + : "Nothing matches the \(model.libraryFilter.rawValue.lowercased()) filter.") ) } else { ContentUnavailableView.search(text: model.globalQuery) @@ -391,27 +393,27 @@ struct LibraryView: View { private var tableWithDescription: some View { Table(model.sortedLibraryRows, selection: $selectedID, sortOrder: $model.librarySort) { - TableColumn("Name", value: \.name) { row in + TableColumn(L10n.string("Name"), value: \.name) { row in nameCell(row) } .width(min: 140, ideal: 200) - TableColumn("Description", value: \.summary) { row in + TableColumn(L10n.string("Description"), value: \.summary) { row in Text(row.summary).foregroundStyle(.secondary).lineLimit(1) } .width(min: 160, ideal: 320) - TableColumn("Version", value: \.version) { row in + TableColumn(L10n.string("Version"), value: \.version) { row in Text(row.version).foregroundStyle(.secondary).monospacedDigit() } .width(min: 80, ideal: 120) - TableColumn("Type", value: \.kind.rawValue) { row in + TableColumn(L10n.string("Type"), value: \.kind.rawValue) { row in KindPill(kind: row.kind) } .width(min: 64, ideal: 80) - TableColumn("Outdated", value: \.outdatedRank) { row in + TableColumn(L10n.string("Outdated"), value: \.outdatedRank) { row in outdatedCell(row) } .width(min: 56, ideal: 72) @@ -421,22 +423,22 @@ struct LibraryView: View { private var tableNoDescription: some View { Table(model.sortedLibraryRows, selection: $selectedID, sortOrder: $model.librarySort) { - TableColumn("Name", value: \.name) { row in + TableColumn(L10n.string("Name"), value: \.name) { row in nameCell(row) } .width(min: 140, ideal: 240) - TableColumn("Version", value: \.version) { row in + TableColumn(L10n.string("Version"), value: \.version) { row in Text(row.version).foregroundStyle(.secondary).monospacedDigit() } .width(min: 80, ideal: 120) - TableColumn("Type", value: \.kind.rawValue) { row in + TableColumn(L10n.string("Type"), value: \.kind.rawValue) { row in KindPill(kind: row.kind) } .width(min: 64, ideal: 80) - TableColumn("Outdated", value: \.outdatedRank) { row in + TableColumn(L10n.string("Outdated"), value: \.outdatedRank) { row in outdatedCell(row) } .width(min: 56, ideal: 72) @@ -475,7 +477,7 @@ struct LibraryView: View { if row.isOutdated { Image(systemName: "arrow.up.circle.fill") .foregroundStyle(.orange) - .help("Update available") + .help(L10n.string("Update available")) } } @@ -563,7 +565,7 @@ struct VulnFooterRow: View { Image(systemName: "circle.fill") .font(.system(size: 8)) .foregroundStyle(.red) - Text("\(model.vulnerableCount) vulnerable package\(model.vulnerableCount == 1 ? "" : "s")") + Text(L10n.vulnerablePackages(model.vulnerableCount)) .font(.callout) .foregroundStyle(.secondary) .lineLimit(1) @@ -574,7 +576,7 @@ struct VulnFooterRow: View { .buttonStyle(.plain) .padding(.horizontal, 16) .padding(.vertical, 8) - .help("\(model.vulnerableCount) installed package\(model.vulnerableCount == 1 ? " has" : "s have") known vulnerabilities. Click to view them in Library.") + .help(L10n.vulnerablePackagesHelp(model.vulnerableCount)) } } } diff --git a/native/Sources/BrewBrowserKit/DashboardView.swift b/native/Sources/BrewBrowserKit/DashboardView.swift index a4875b9..b80d7b6 100644 --- a/native/Sources/BrewBrowserKit/DashboardView.swift +++ b/native/Sources/BrewBrowserKit/DashboardView.swift @@ -17,7 +17,7 @@ struct DashboardView: View { var body: some View { ScrollView { if !model.dashboardLoaded { - ProgressView("Reading your Homebrew setup…") + ProgressView(L10n.isRussian ? "Читаем конфигурацию Homebrew…" : "Reading your Homebrew setup…") .frame(maxWidth: .infinity, minHeight: 300) } else { VStack(alignment: .leading, spacing: 16) { @@ -89,8 +89,8 @@ struct HeroStrip: View { model.openLibrary() } StatTile( - value: model.outdatedLoading ? "…" : (model.outdatedCount == 0 ? "All current" : "\(model.outdatedCount)"), - label: model.outdatedLoading ? "checking updates" : (model.outdatedCount == 0 ? "" : "updates available"), + value: model.outdatedLoading ? "…" : (model.outdatedCount == 0 ? L10n.display("All current") : "\(model.outdatedCount)"), + label: model.outdatedLoading ? L10n.display("checking updates") : (model.outdatedCount == 0 ? "" : L10n.display("updates available")), symbol: "arrow.up.circle" ) { if model.outdatedCount > 0 { model.openOutdatedInLibrary() } } .disabled(model.outdatedLoading || model.outdatedCount == 0) @@ -116,7 +116,7 @@ struct StatTile: View { .font(monospaceValue ? .title3.weight(.semibold).monospaced() : .title2.weight(.semibold)) .lineLimit(1).minimumScaleFactor(0.5) if !label.isEmpty { - Text(label).font(.caption).foregroundStyle(.secondary).lineLimit(1).truncationMode(.middle) + Text(L10n.display(label)).font(.caption).foregroundStyle(.secondary).lineLimit(1).truncationMode(.middle) } } Spacer(minLength: 0) @@ -140,10 +140,10 @@ struct CatalogFreshnessStrip: View { var body: some View { let stale = model.catalogIsStale HStack(spacing: 8) { - Text("Catalog: \(Text(model.catalogDaysOldLabel).fontWeight(.semibold))") + Text("\(L10n.display("Catalog")): \(Text(model.catalogDaysOldLabel).fontWeight(.semibold))") .foregroundStyle(.secondary) if let src = model.catalogSummary?.source { - Text("(\(src.rawValue))") + Text("(\(L10n.display(src.rawValue)))") .foregroundStyle(.secondary) } Spacer() @@ -160,17 +160,17 @@ struct CatalogFreshnessStrip: View { // shows real motion while the sync runs. HStack(spacing: 5) { ProgressView().controlSize(.small) - Text("Refreshing…") + Text(L10n.display("Refreshing…")) } } else if stale { - Label("Refresh from brew.sh →", systemImage: "arrow.clockwise") + Label(L10n.display("Refresh from brew.sh →"), systemImage: "arrow.clockwise") } else { - Label("Refresh", systemImage: "arrow.clockwise") + Label(L10n.display("Refresh"), systemImage: "arrow.clockwise") } } .controlSize(.small) .disabled(model.catalogRefreshing) - .help("Re-download the Homebrew catalog from formulae.brew.sh") + .help(L10n.isRussian ? "Заново загрузить каталог Homebrew с formulae.brew.sh" : "Re-download the Homebrew catalog from formulae.brew.sh") } .font(.callout) .padding(.horizontal, 12) @@ -197,7 +197,7 @@ struct UpdatesCard: View { model.openOutdatedInLibrary() } label: { HStack(spacing: 4) { - Text("Updates available").font(.headline) + Text(L10n.display("Updates available")).font(.headline) Image(systemName: "arrow.right").font(.caption) } } @@ -207,12 +207,12 @@ struct UpdatesCard: View { // `brew update` + re-pulls the catalog, so a separate Update // was redundant (and Tauri's Updates card has only Choose… + // Upgrade all). - Button("Choose…") { showUpgradeSheet = true } + Button(L10n.display("Choose…")) { showUpgradeSheet = true } .controlSize(.small) Button { Task { await model.upgradeAll(greedy: LocalPrefs.shared.greedyUpgrade) } } label: { - Label("Upgrade all (\(model.outdatedCount))", systemImage: "arrow.up.circle") + Label(L10n.isRussian ? "Обновить всё (\(model.outdatedCount))" : "Upgrade all (\(model.outdatedCount))", systemImage: "arrow.up.circle") } .controlSize(.small) .buttonStyle(.borderedProminent) @@ -266,7 +266,7 @@ struct UpdatesCard: View { if model.outdatedCount > model.outdatedPreview.count { HStack { - Button("+ \(model.outdatedCount - model.outdatedPreview.count) more in Library") { + Button(L10n.moreInLibrary(model.outdatedCount - model.outdatedPreview.count)) { model.openOutdatedInLibrary() } .buttonStyle(.link) @@ -287,7 +287,7 @@ struct UpdatesCard: View { struct KindPill: View { let kind: InstalledPackage.Kind var body: some View { - Text(kind.rawValue) + Text(L10n.packageKind(kind)) .font(.caption2.weight(.medium)) .padding(.horizontal, 7).padding(.vertical, 2) .background(kind == .formula ? Color.blue.opacity(0.18) : Color.orange.opacity(0.18), @@ -308,7 +308,7 @@ struct CompositionCard: View { private var formulaFraction: CGFloat { CGFloat(model.formulaCount) / CGFloat(total) } private var pieData: [(label: String, count: Int)] { - [("Formulae", model.formulaCount), ("Casks", model.caskCount)] + [(L10n.display("Formulae"), model.formulaCount), (L10n.display("Casks"), model.caskCount)] } private var formulaPct: Double { Double(model.formulaCount) / Double(total) * 100 } @@ -319,10 +319,10 @@ struct CompositionCard: View { VStack(alignment: .leading, spacing: 12) { // Title row — pills (on request / pinned) live here now. HStack { - Text("Composition").font(.headline) + Text(L10n.display("Composition")).font(.headline) Spacer() - Chip(text: "\(model.onRequestCount) on request") - Chip(text: "\(model.pinnedCount) pinned") + Chip(text: L10n.display("\(model.onRequestCount) on request")) + Chip(text: L10n.display("\(model.pinnedCount) pinned")) } if compact { @@ -333,16 +333,16 @@ struct CompositionCard: View { .cornerRadius(4) .foregroundStyle(by: .value("Kind", item.label)) } - .chartForegroundStyleScale(["Formulae": Color.blue, "Casks": Color.orange]) + .chartForegroundStyleScale([L10n.display("Formulae"): Color.blue, L10n.display("Casks"): Color.orange]) .chartLegend(.hidden) .frame(width: 180, height: 180) .padding(.vertical, 16) .padding(.leading, 16) VStack(spacing: 0) { - compositionLegendRow(color: .blue, label: "Formulae", + compositionLegendRow(color: .blue, label: L10n.display("Formulae"), count: model.formulaCount, pct: formulaPct) - compositionLegendRow(color: .orange, label: "Casks", + compositionLegendRow(color: .orange, label: L10n.display("Casks"), count: model.caskCount, pct: caskPct) } .frame(maxWidth: .infinity) @@ -362,8 +362,8 @@ struct CompositionCard: View { .frame(height: 14) HStack(spacing: 16) { - LegendDot(color: .blue, text: "\(model.formulaCount) formulae") - LegendDot(color: .orange, text: "\(model.caskCount) casks") + LegendDot(color: .blue, text: L10n.formulaeCount(model.formulaCount)) + LegendDot(color: .orange, text: L10n.casksCount(model.caskCount)) Spacer() } } @@ -426,7 +426,7 @@ struct CategoriesCard: View { var body: some View { GroupBox { VStack(alignment: .leading, spacing: 12) { - Text("Top categories in your library").font(.headline) + Text(L10n.display("Top categories in your library")).font(.headline) .frame(maxWidth: .infinity, alignment: .leading) HStack(alignment: .center, spacing: 20) { @@ -469,7 +469,7 @@ struct CategoriesCard: View { .font(.caption) .foregroundStyle(.secondary) .frame(width: 16) - Text(cat.label) + Text(L10n.display(cat.label)) .font(.callout) .fontWeight(hovered == cat.slug ? .semibold : .regular) Spacer(minLength: 12) @@ -493,7 +493,7 @@ struct CategoriesCard: View { // Click a category → show your INSTALLED packages in it (Library), // not the whole Discover catalog. See #58. .onTapGesture { model.jumpToLibraryCategory(cat.slug) } - .help("Show your installed \(cat.label) packages") + .help(L10n.isRussian ? "Показать установленные пакеты в категории \(L10n.display(cat.label))" : "Show your installed \(cat.label) packages") } } @@ -518,9 +518,9 @@ struct StorageCard: View { GroupBox { VStack(spacing: 0) { HStack { - Text("Storage").font(.headline) + Text(L10n.display("Storage")).font(.headline) Spacer() - Text("\(human(model.storageTotalBytes)) total") + Text(L10n.display("\(human(model.storageTotalBytes)) total")) .font(.callout).foregroundStyle(.secondary) } .padding(.bottom, 8) @@ -528,14 +528,14 @@ struct StorageCard: View { if model.storage.isEmpty && model.storageLoading { HStack(spacing: 8) { ProgressView().controlSize(.small) - Text("Measuring disk usage…").font(.callout).foregroundStyle(.secondary) + Text(L10n.display("Measuring disk usage…")).font(.callout).foregroundStyle(.secondary) Spacer() } .padding(.vertical, 7) } ForEach(model.storage) { item in HStack(spacing: 12) { - Text(item.label).frame(width: 160, alignment: .leading) + Text(L10n.display(item.label)).frame(width: 160, alignment: .leading) Text(item.path) .font(.callout.monospaced()).foregroundStyle(.secondary) .lineLimit(1).truncationMode(.middle) @@ -559,7 +559,7 @@ struct StorageCard: View { Button { Task { await model.runDoctor() } } label: { - Label(model.doctorRunning ? "Running…" : "Run brew doctor", + Label(model.doctorRunning ? L10n.display("Running…") : L10n.display("Run brew doctor"), systemImage: "stethoscope") } .disabled(model.maintenanceBusy) @@ -567,13 +567,13 @@ struct StorageCard: View { Button { showCleanupConfirm.toggle() } label: { - Label(model.cleanupRunning ? "Cleaning…" : "Clean up cache…", + Label(model.cleanupRunning ? L10n.display("Cleaning…") : L10n.display("Clean up cache…"), systemImage: "trash") } .disabled(model.maintenanceBusy) if let bytes = model.cleanupReclaimableBytes, bytes > 0 { - Text("frees ~\(human(bytes))") + Text(L10n.display("frees ~\(human(bytes))")) .font(.callout).foregroundStyle(.secondary) } Spacer() @@ -585,14 +585,14 @@ struct StorageCard: View { Text(cleanupConfirmText) .font(.callout).foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) - Toggle("Scrub — also remove the latest versions' cached downloads (more aggressive)", isOn: $cleanupScrub) + Toggle(L10n.display("Scrub — also remove the latest versions' cached downloads (more aggressive)"), isOn: $cleanupScrub) .font(.callout) - Toggle("Verbose — list every file removed", isOn: $cleanupVerbose) + Toggle(L10n.display("Verbose — list every file removed"), isOn: $cleanupVerbose) .font(.callout) HStack { Spacer() - Button("Cancel") { showCleanupConfirm = false } - Button("Clean up", role: .destructive) { + Button(L10n.display("Cancel")) { showCleanupConfirm = false } + Button(L10n.display("Clean up"), role: .destructive) { showCleanupConfirm = false Task { await model.runCleanup(scrub: cleanupScrub, verbose: cleanupVerbose) } } @@ -612,13 +612,17 @@ struct StorageCard: View { private var cleanupConfirmText: String { let freeing: String if let bytes = model.cleanupReclaimableBytes, bytes > 0 { - freeing = ", freeing about \(human(bytes))" + freeing = L10n.isRussian ? ", освободится примерно \(human(bytes))" : ", freeing about \(human(bytes))" } else { freeing = "" } let scrubNote = cleanupScrub - ? " Also clears the current versions' downloads (--scrub)." + ? (L10n.isRussian ? " Также удалит загрузки текущих версий (--scrub)." : " Also clears the current versions' downloads (--scrub).") : "" + if L10n.isRussian { + return "Удаляет устаревшие кэшированные загрузки\(freeing).\(scrubNote) " + + "Установленные пакеты не затрагиваются." + } return "Removes outdated cached downloads\(freeing).\(scrubNote) " + "Your installed packages are not affected." } @@ -638,7 +642,7 @@ struct ExposureCard: View { /// "Last scan: 2 hours ago" / "never". Same RelativeDateTimeFormatter UX /// language as the Tauri card's `Intl.RelativeTimeFormat`. private var lastScanLabel: String { - guard let at = model.vulnLastScannedAt else { return "never" } + guard let at = model.vulnLastScannedAt else { return L10n.isRussian ? "никогда" : "never" } let fmt = RelativeDateTimeFormatter() fmt.unitsStyle = .full return fmt.localizedString(for: at, relativeTo: Date()) @@ -656,10 +660,10 @@ struct ExposureCard: View { HStack { Image(systemName: freshClean ? "checkmark.shield.fill" : "exclamationmark.shield.fill") .foregroundStyle(freshClean ? .green : .orange) - Text("Exposure").font(.headline) + Text(L10n.string("Exposure")).font(.headline) Spacer() if scanned { - Text("Last scan: \(lastScanLabel)") + Text(L10n.isRussian ? "Последняя проверка: \(lastScanLabel)" : "Last scan: \(lastScanLabel)") .font(.caption).foregroundStyle(.secondary) } Button { @@ -669,15 +673,15 @@ struct ExposureCard: View { // Live spinner, not a static rotate-arrow. HStack(spacing: 5) { ProgressView().controlSize(.small) - Text("Scanning…") + Text(L10n.string("Scanning…")) } } else { - Label("Scan now", systemImage: "arrow.clockwise") + Label(L10n.string("Scan now"), systemImage: "arrow.clockwise") } } .controlSize(.small) .disabled(model.vulnScanAllLoading) - .help("Re-run brew vulns against every installed formula") + .help(L10n.isRussian ? "Повторно запустить brew vulns для всех установленных формул" : "Re-run brew vulns against every installed formula") } if !scanned { @@ -685,8 +689,10 @@ struct ExposureCard: View { // not imply safety we haven't verified. Label { VStack(alignment: .leading, spacing: 2) { - Text("Not scanned yet.").fontWeight(.semibold) - Text("We haven't checked your installed packages for known vulnerabilities. Run a scan when you can. (Vulnerability scanning is configured in Settings → Security.)") + Text(L10n.string("Not scanned yet.")).fontWeight(.semibold) + Text(L10n.isRussian + ? "Установленные пакеты ещё не проверялись на известные уязвимости. Запустите проверку, когда будет удобно. Настройка находится в «Настройки → Безопасность»." + : "We haven't checked your installed packages for known vulnerabilities. Run a scan when you can. (Vulnerability scanning is configured in Settings → Security.)") .font(.caption).foregroundStyle(.secondary) } } icon: { @@ -699,8 +705,8 @@ struct ExposureCard: View { // re-scan prompt. Label { VStack(alignment: .leading, spacing: 2) { - Text("No advisories as of the last scan (\(lastScanLabel)).").fontWeight(.semibold) - Text("Packages may have changed since. Re-scan to confirm.") + Text(L10n.isRussian ? "На момент последней проверки (\(lastScanLabel)) записей об уязвимостях нет." : "No advisories as of the last scan (\(lastScanLabel)).").fontWeight(.semibold) + Text(L10n.isRussian ? "С тех пор пакеты могли измениться. Проверьте ещё раз для подтверждения." : "Packages may have changed since. Re-scan to confirm.") .font(.caption).foregroundStyle(.secondary) } } icon: { @@ -710,8 +716,8 @@ struct ExposureCard: View { // Freshly clean — a GOOD result; frame it positively. Label { VStack(alignment: .leading, spacing: 2) { - Text("No known vulnerabilities.").fontWeight(.semibold) - Text("All installed packages are clean of advisories known to brew vulns.") + Text(L10n.string("No known vulnerabilities.")).fontWeight(.semibold) + Text(L10n.isRussian ? "Для установленных пакетов нет уязвимостей, известных brew vulns." : "All installed packages are clean of advisories known to brew vulns.") .font(.caption).foregroundStyle(.secondary) } } icon: { @@ -724,11 +730,11 @@ struct ExposureCard: View { // medium warning (amber/orange), low info (blue), unknown // neutral (gray). HStack(spacing: 16) { - sevCount(exposure.critical, "critical", .red) - sevCount(exposure.high, "high", .red) - sevCount(exposure.medium, "medium", .orange) - sevCount(exposure.low, "low", .blue) - if exposure.unknown > 0 { sevCount(exposure.unknown, "unknown", .gray) } + sevCount(exposure.critical, L10n.severity(.critical), .red) + sevCount(exposure.high, L10n.severity(.high), .red) + sevCount(exposure.medium, L10n.severity(.medium), .orange) + sevCount(exposure.low, L10n.severity(.low), .blue) + if exposure.unknown > 0 { sevCount(exposure.unknown, L10n.severity(.unknown), .gray) } Spacer() } // Summary line — identical to the Tauri Exposure card @@ -737,9 +743,14 @@ struct ExposureCard: View { // advisories (findings), so the line spells out the findings // total separately from the package count; the source label // makes a stale (cache) scan visibly distinct from a fresh one. - Text("**\(exposure.total)** finding\(exposure.total == 1 ? "" : "s") across **\(exposure.vulnerablePackages)** of **\(model.totalPackages)** installed packages\(model.vulnSource.map { " · source: \($0.rawValue)" } ?? "")") + Text(L10n.exposureSummary( + findings: exposure.total, + vulnerablePackages: exposure.vulnerablePackages, + totalPackages: model.totalPackages, + source: model.vulnSource?.rawValue + )) .font(.callout).foregroundStyle(.secondary) - Button("View vulnerable packages →") { + Button(L10n.string("security.viewVulnerablePackages")) { model.openVulnerableInLibrary() } .buttonStyle(.link) @@ -782,22 +793,20 @@ struct GitHubCard: View { .padding(.bottom, 10) if model.githubStatsLoaded && model.githubHomepageTotal == 0 { - Text("None of your installed packages have a GitHub homepage.") + Text(L10n.string("github.noInstalledHomepages")) .font(.callout).foregroundStyle(.secondary) .frame(maxWidth: .infinity, alignment: .leading) } else if !model.githubStatsLoaded { HStack(spacing: 8) { ProgressView().controlSize(.small) - Text(model.githubHomepageTotal > 0 - ? "Checking which of your \(model.githubHomepageTotal) packages you've starred…" - : "Checking your starred packages…") + Text(L10n.githubCheckingStarred(total: model.githubHomepageTotal)) .font(.callout).foregroundStyle(.secondary) Spacer() } } else { HStack(spacing: 8) { Image(systemName: "star.fill").foregroundStyle(.yellow) - Text("You've starred **\(model.githubStarredCount)** of **\(model.githubHomepageTotal)** installed packages with GitHub homepages.") + Text(L10n.githubStarredSummary(starred: model.githubStarredCount, total: model.githubHomepageTotal)) .font(.callout) Spacer() } diff --git a/native/Sources/BrewBrowserKit/Deprecation.swift b/native/Sources/BrewBrowserKit/Deprecation.swift index 0fdecae..91c57aa 100644 --- a/native/Sources/BrewBrowserKit/Deprecation.swift +++ b/native/Sources/BrewBrowserKit/Deprecation.swift @@ -72,7 +72,9 @@ enum DeprecationBadgeKind: String, Sendable { case disabled /// Short label for the row badge + detail notice header. - var label: String { self == .disabled ? "Disabled" : "Deprecated" } + var label: String { + return self == .disabled ? "Disabled" : "Deprecated" + } } /// Collapse the formula/cask replacement variants into one token: prefer the diff --git a/native/Sources/BrewBrowserKit/DiscoverView.swift b/native/Sources/BrewBrowserKit/DiscoverView.swift index 61115d5..3a96188 100644 --- a/native/Sources/BrewBrowserKit/DiscoverView.swift +++ b/native/Sources/BrewBrowserKit/DiscoverView.swift @@ -25,7 +25,7 @@ struct DiscoverView: View { var body: some View { Group { if model.catalogLoading && model.catalog.isEmpty { - ProgressView("Loading the Homebrew catalog…") + ProgressView(L10n.string("Loading the Homebrew catalog…")) .frame(maxWidth: .infinity, maxHeight: .infinity) } else { VStack(spacing: 0) { @@ -59,7 +59,7 @@ struct DiscoverView: View { HStack(spacing: 10) { Image(systemName: "exclamationmark.triangle.fill") .foregroundStyle(.orange) - Text("Catalog is \(Text(model.catalogDaysOldLabel).fontWeight(.semibold)). Newer packages and deprecations may be missing.") + Text(L10n.catalogStaleBanner(age: model.catalogDaysOldLabel)) .font(.callout) Spacer() Button { @@ -68,10 +68,10 @@ struct DiscoverView: View { if model.catalogRefreshing { HStack(spacing: 5) { ProgressView().controlSize(.small) - Text("Refreshing…") + Text(L10n.display("Refreshing…")) } } else { - Label("Refresh from brew.sh →", systemImage: "arrow.clockwise") + Label(L10n.display("Refresh from brew.sh →"), systemImage: "arrow.clockwise") } } .controlSize(.small) @@ -82,7 +82,7 @@ struct DiscoverView: View { Image(systemName: "xmark") } .buttonStyle(.borderless) - .help("Dismiss for this session") + .help(L10n.string("Dismiss for this session")) } .padding(.horizontal, 12) .padding(.vertical, 8) @@ -91,10 +91,10 @@ struct DiscoverView: View { // Category Picker, centered (matches Library's centered segmented filter). private var filterBar: some View { - Picker("Category", selection: $model.discoverCategory) { - Text("All Categories").tag(String?.none) + Picker(L10n.isRussian ? "Категория" : "Category", selection: $model.discoverCategory) { + Text(L10n.isRussian ? "Все категории" : "All Categories").tag(String?.none) ForEach(model.categoryList, id: \.slug) { cat in - Text(cat.label).tag(String?.some(cat.slug)) + Text(L10n.display(cat.label)).tag(String?.some(cat.slug)) } } .pickerStyle(.menu) @@ -144,9 +144,9 @@ struct DiscoverView: View { VStack(alignment: .leading, spacing: 0) { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { - subgroupChip(label: "All", count: nil, key: nil) + subgroupChip(label: L10n.isRussian ? "Все" : "All", count: nil, key: nil) ForEach(groups) { g in - subgroupChip(label: g.label, count: g.count, key: g.key) + subgroupChip(label: L10n.display(g.label), count: g.count, key: g.key) } } .padding(.horizontal, 12) @@ -174,8 +174,8 @@ struct DiscoverView: View { } .buttonStyle(.plain) .help(key == categorySubgroupGeneralKey - ? "Members of this category with no other category" - : "Members also categorized here") + ? L10n.string("Members of this category with no other category") + : L10n.string("Members also categorized here")) } // Recent-search chips — click to re-run. Persisted in LocalPrefs @@ -183,11 +183,11 @@ struct DiscoverView: View { private var recentSearchesRow: some View { VStack(alignment: .leading, spacing: 8) { HStack { - Text("RECENT") + Text(L10n.string("RECENT")) .font(.caption.weight(.semibold)) .foregroundStyle(.secondary) Spacer() - Button("Clear") { prefs.clearRecentSearches() } + Button(L10n.string("Clear")) { prefs.clearRecentSearches() } .buttonStyle(.link) .font(.caption) } @@ -213,7 +213,7 @@ struct DiscoverView: View { private var categoryGrid: some View { let columns = [GridItem(.adaptive(minimum: 150, maximum: 240), spacing: 12)] return VStack(alignment: .leading, spacing: 8) { - Text("Browse \(model.catalog.count) packages by category, or search above.") + Text(L10n.browsePackagesByCategory(model.catalog.count)) .font(.callout).foregroundStyle(.secondary) LazyVGrid(columns: columns, spacing: 12) { ForEach(model.categoryTiles) { tile in @@ -224,7 +224,7 @@ struct DiscoverView: View { Image(systemName: tile.icon) .font(.title2) .foregroundStyle(.tint) - Text(tile.label) + Text(L10n.display(tile.label)) .font(.callout.weight(.medium)) .multilineTextAlignment(.center) .lineLimit(2) @@ -247,15 +247,15 @@ struct DiscoverView: View { let rows = model.sortedDiscoverRows if rows.isEmpty { if model.catalog.isEmpty { - ContentUnavailableView("Catalog unavailable", + ContentUnavailableView(L10n.string("Catalog unavailable"), systemImage: "exclamationmark.triangle", - description: Text("The bundled package catalog couldn't be loaded.")) + description: Text(L10n.string("The bundled package catalog couldn't be loaded."))) } else if !model.globalQuery.isEmpty { ContentUnavailableView.search(text: model.globalQuery) } else { - ContentUnavailableView("No packages", + ContentUnavailableView(L10n.string("No packages"), systemImage: "sparkles.rectangle.stack", - description: Text("Nothing in this category.")) + description: Text(L10n.isRussian ? "В этой категории ничего нет." : "Nothing in this category.")) } } else { // AI-gated Description column → two static column sets (a conditional @@ -273,25 +273,25 @@ struct DiscoverView: View { private func discoverTable(_ rows: [DiscoverRow], showDescription: Bool) -> some View { if showDescription { Table(rows, selection: $selectedID, sortOrder: $model.discoverSort) { - TableColumn("Name", value: \.name) { iconNameCell($0) }.width(min: 160, ideal: 220) - TableColumn("Description", value: \.summary) { r in + TableColumn(L10n.string("Name"), value: \.name) { iconNameCell($0) }.width(min: 160, ideal: 220) + TableColumn(L10n.string("Description"), value: \.summary) { r in Text(r.summary).foregroundStyle(.secondary).lineLimit(1) }.width(min: 160, ideal: 300) - TableColumn("Version", value: \.version) { r in + TableColumn(L10n.string("Version"), value: \.version) { r in Text(r.version).foregroundStyle(.secondary).monospacedDigit() }.width(min: 70, ideal: 100) - TableColumn("Type", value: \.kind.rawValue) { KindPill(kind: $0.kind) }.width(min: 64, ideal: 80) - TableColumn("Installed", value: \.installedRank) { installedCell($0) }.width(min: 64, ideal: 80) + TableColumn(L10n.string("Type"), value: \.kind.rawValue) { KindPill(kind: $0.kind) }.width(min: 64, ideal: 80) + TableColumn(L10n.string("Installed"), value: \.installedRank) { installedCell($0) }.width(min: 64, ideal: 80) } .onChange(of: selectedID, openSelected) } else { Table(rows, selection: $selectedID, sortOrder: $model.discoverSort) { - TableColumn("Name", value: \.name) { iconNameCell($0) }.width(min: 200, ideal: 280) - TableColumn("Version", value: \.version) { r in + TableColumn(L10n.string("Name"), value: \.name) { iconNameCell($0) }.width(min: 200, ideal: 280) + TableColumn(L10n.string("Version"), value: \.version) { r in Text(r.version).foregroundStyle(.secondary).monospacedDigit() }.width(min: 70, ideal: 100) - TableColumn("Type", value: \.kind.rawValue) { KindPill(kind: $0.kind) }.width(min: 64, ideal: 80) - TableColumn("Installed", value: \.installedRank) { installedCell($0) }.width(min: 64, ideal: 80) + TableColumn(L10n.string("Type"), value: \.kind.rawValue) { KindPill(kind: $0.kind) }.width(min: 64, ideal: 80) + TableColumn(L10n.string("Installed"), value: \.installedRank) { installedCell($0) }.width(min: 64, ideal: 80) } .onChange(of: selectedID, openSelected) } @@ -325,7 +325,7 @@ struct DiscoverView: View { if row.isInstalled { Image(systemName: "checkmark.circle.fill") .foregroundStyle(.green) - .help("Installed") + .help(L10n.string("Installed")) } } diff --git a/native/Sources/BrewBrowserKit/Enrichment.swift b/native/Sources/BrewBrowserKit/Enrichment.swift index 7fee455..823426e 100644 --- a/native/Sources/BrewBrowserKit/Enrichment.swift +++ b/native/Sources/BrewBrowserKit/Enrichment.swift @@ -2,8 +2,11 @@ import Foundation /// Loads the bundled `enrichment.json` (Homebrew token → LLM-derived /// enrichment record) and exposes a per-package lookup — the same offline -/// data the Tauri app bakes in via `tools/enrich/enrich.py`. There is no -/// runtime LLM request; everything ships in the bundle. +/// data the Tauri app bakes in via `tools/enrich/enrich.py`. Locale overlays +/// such as `enrichment.ru.json` are partial files keyed by the same tokens; +/// they replace user-facing prose and add locale search terms while keeping +/// stable package IDs and English fallbacks. There is no runtime LLM request; +/// everything ships in the bundle. /// /// Mirrors the `Categories.swift` loading pattern: `Bundle.module` resource /// URL → `Data` → `JSONSerialization`, decoded defensively so missing or @@ -24,6 +27,8 @@ struct EnrichmentEntry: Sendable, Hashable { let similar: [String] /// Tech-stack tags (capped at 12). Lowercase, hyphenated, `[a-z0-9-]` only. let tags: [String] + /// Locale-specific search aliases. Not shown as tags. + let searchTerms: [String] } struct EnrichmentCatalog: Sendable { @@ -39,12 +44,14 @@ struct EnrichmentCatalog: Sendable { private static let maxSimilarCount = 50 private static let maxTagsCount = 12 private static let maxTagLen = 30 + private static let maxSearchTermLen = 60 + private static let maxSearchTermsCount = 16 /// Decode the bundled JSON. Returns `nil` if the resource is missing or /// malformed (callers then just render no enriched data). Per-field caps /// are re-applied here so a swapped bundle can't smuggle oversized fields, /// matching the Rust loader's defense-in-depth posture. - static func loadBundled() -> EnrichmentCatalog? { + static func loadBundled(locale: String? = nil) -> EnrichmentCatalog? { guard let url = Bundle.module.url(forResource: "enrichment", withExtension: "json"), let data = try? Data(contentsOf: url), let root = try? JSONSerialization.jsonObject(with: data) as? [String: Any] @@ -61,6 +68,7 @@ struct EnrichmentCatalog: Sendable { guard let obj = value as? [String: Any] else { continue } entries[name] = parseEntry(obj) } + applyLocaleOverlay(normalizeLocale(locale), to: &entries) return EnrichmentCatalog(entries: entries) } @@ -94,7 +102,8 @@ struct EnrichmentCatalog: Sendable { summary: summary, useCases: Array(useCases), similar: similar, - tags: tags + tags: tags, + searchTerms: parseSearchTerms(obj) ) } @@ -139,7 +148,40 @@ struct EnrichmentCatalog: Sendable { summary: summary, useCases: Array(useCases), similar: similar, - tags: tags + tags: tags, + searchTerms: parseSearchTerms(obj) + ) + } + + private static func normalizeLocale(_ locale: String?) -> String { + guard let locale else { return "en" } + let lc = locale.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() + return lc == "ru" || lc.hasPrefix("ru-") || lc.hasPrefix("ru_") ? "ru" : "en" + } + + private static func applyLocaleOverlay(_ locale: String, to entries: inout [String: EnrichmentEntry]) { + guard locale == "ru", + let url = Bundle.module.url(forResource: "enrichment.ru", withExtension: "json"), + let data = try? Data(contentsOf: url), + let root = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + normalizeLocale(root["locale"] as? String) == locale, + let rawEntries = root["entries"] as? [String: Any] + else { return } + + for (name, value) in rawEntries { + guard let obj = value as? [String: Any] else { continue } + let patch = parseEntry(obj) + entries[name] = patch.localized(over: entries[name]) + } + } + + private static func parseSearchTerms(_ obj: [String: Any]) -> [String] { + let raw = (obj["search_terms"] as? [String]) ?? (obj["searchTerms"] as? [String]) ?? [] + return Array( + raw + .prefix(maxSearchTermsCount) + .map { truncate($0, maxSearchTermLen) } + .filter { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } ) } @@ -171,7 +213,7 @@ struct EnrichmentCatalog: Sendable { } } -private extension EnrichmentEntry { +extension EnrichmentEntry { /// True when the record carries no usable data — used to treat a present /// but empty record the same as an absent one. var isEmpty: Bool { @@ -180,5 +222,20 @@ private extension EnrichmentEntry { && useCases.isEmpty && similar.isEmpty && tags.isEmpty + && searchTerms.isEmpty + } + + func localized(over base: EnrichmentEntry?) -> EnrichmentEntry { + EnrichmentEntry( + friendlyName: friendlyName.flatMap { $0.isEmpty ? nil : $0 } ?? base?.friendlyName, + summary: summary.flatMap { $0.isEmpty ? nil : $0 } ?? base?.summary, + useCases: useCases.isEmpty ? (base?.useCases ?? []) : useCases, + // Stable fields stay sourced from the base bundle. Locale overlays + // may carry prose/search aliases, but never translated package-token + // suggestions or technical tag labels. + similar: base?.similar ?? [], + tags: base?.tags ?? [], + searchTerms: searchTerms.isEmpty ? (base?.searchTerms ?? []) : searchTerms + ) } } diff --git a/native/Sources/BrewBrowserKit/GitHubService.swift b/native/Sources/BrewBrowserKit/GitHubService.swift index 846e388..99e5460 100644 --- a/native/Sources/BrewBrowserKit/GitHubService.swift +++ b/native/Sources/BrewBrowserKit/GitHubService.swift @@ -139,13 +139,93 @@ public struct DeviceFlowStart: Sendable, Hashable { /// path in `github.rs:180-182` /// - `http(_)` ⟵ `BrewError::HttpStatus { status }` /// - `network(_)` ⟵ `BrewError::Network { message }` -public enum GithubError: Error, Sendable, Equatable { +public enum GithubError: Error, LocalizedError, Sendable, Equatable { case authRequired case scopeRequired(String) case rateLimited(resetAt: String?) case notAGithubURL case http(Int) case network(String) + + public var errorDescription: String? { + switch self { + case .authRequired: + return L10n.isRussian + ? "Для этого действия нужно войти в GitHub." + : "GitHub sign-in is required for this action." + case .scopeRequired(let scope): + return L10n.isRussian + ? "GitHub-токену не хватает разрешения \(scope). Войдите в GitHub ещё раз." + : "GitHub token is missing the \(scope) scope. Sign in again." + case .rateLimited(let resetAt): + if L10n.isRussian { + if let resetAt { return "GitHub ограничил частоту запросов. Повторите после \(resetAt)." } + return "GitHub ограничил частоту запросов. Повторите позже." + } + if let resetAt { return "GitHub rate limit reached. Try again after \(resetAt)." } + return "GitHub rate limit reached. Try again later." + case .notAGithubURL: + return L10n.isRussian + ? "Это не ссылка GitHub вида github.com//." + : "This is not a GitHub URL in the form github.com//." + case .http(let status): + return L10n.isRussian + ? "GitHub вернул HTTP \(status)." + : "GitHub returned HTTP \(status)." + case .network(let message): + if L10n.isRussian { + switch message { + case "access denied": + return "Авторизация GitHub отклонена." + case "device code expired": + return "Код входа GitHub истёк. Начните вход заново." + case "malformed device/code response": + return "GitHub вернул некорректный ответ для device flow." + case "device flow returned neither access_token nor error": + return "GitHub не вернул ни токен доступа, ни ошибку device flow." + case "malformed repo response": + return "GitHub вернул некорректные данные репозитория." + case "create_issue returned no number/html_url": + return "GitHub создал issue, но не вернул номер или ссылку." + case "/user returned empty login": + return "GitHub не вернул имя пользователя." + case "issue title must not be empty": + return "Заголовок issue не может быть пустым." + default: + if message.hasPrefix("github device flow error: ") { + let raw = String(message.dropFirst("github device flow error: ".count)) + return "GitHub вернул ошибку device flow: \(raw)." + } + if message.hasPrefix("bad url: ") { + let url = String(message.dropFirst("bad url: ".count)) + return "Некорректный URL GitHub: \(url)." + } + if message.hasPrefix("non-HTTP response from ") { + let url = String(message.dropFirst("non-HTTP response from ".count)) + return "GitHub вернул не-HTTP ответ от \(url)." + } + if message.hasPrefix("issue title exceeds ") { + return "Заголовок issue слишком длинный." + } + if message.hasPrefix("issue body exceeds ") { + return "Текст issue слишком длинный." + } + if message.hasPrefix("too many labels") { + return "Слишком много меток issue." + } + if message.hasPrefix("label length must be ") { + return "Недопустимая длина метки issue." + } + if message.hasPrefix("label contains invalid character: ") { + let label = String(message.dropFirst("label contains invalid character: ".count)) + return "Метка issue содержит недопустимый символ: \(label)." + } + return "Ошибка GitHub: \(message)" + } + } + return message + } + } } // MARK: - GitHubService diff --git a/native/Sources/BrewBrowserKit/InfoButton.swift b/native/Sources/BrewBrowserKit/InfoButton.swift index 2c19032..16d5420 100644 --- a/native/Sources/BrewBrowserKit/InfoButton.swift +++ b/native/Sources/BrewBrowserKit/InfoButton.swift @@ -37,7 +37,7 @@ struct InfoButton: View { open = false onReport() } label: { - Label("Report an issue on GitHub", systemImage: "exclamationmark.bubble") + Label(L10n.string("Report an issue on GitHub"), systemImage: "exclamationmark.bubble") } .controlSize(.small) } diff --git a/native/Sources/BrewBrowserKit/LocalPrefs.swift b/native/Sources/BrewBrowserKit/LocalPrefs.swift index 52cadf8..8b32151 100644 --- a/native/Sources/BrewBrowserKit/LocalPrefs.swift +++ b/native/Sources/BrewBrowserKit/LocalPrefs.swift @@ -33,9 +33,9 @@ enum AppTheme: String, CaseIterable, Sendable { /// Capitalized, human-readable label for menus / pickers. var label: String { switch self { - case .light: return "Light" - case .dark: return "Dark" - case .system: return "System" + case .light: return L10n.string("theme.light") + case .dark: return L10n.string("theme.dark") + case .system: return L10n.string("theme.system") } } } @@ -53,7 +53,15 @@ enum LandingSection: String, CaseIterable, Sendable { /// Capitalized, human-readable label (e.g. "Dashboard"). var label: String { - rawValue.capitalized + switch self { + case .dashboard: return L10n.string("nav.dashboard") + case .library: return L10n.string("nav.library") + case .discover: return L10n.string("nav.discover") + case .trending: return L10n.string("nav.trending") + case .snapshots: return L10n.string("nav.snapshots") + case .services: return L10n.string("nav.services") + case .activity: return L10n.string("nav.activity") + } } } diff --git a/native/Sources/BrewBrowserKit/Localization.swift b/native/Sources/BrewBrowserKit/Localization.swift new file mode 100644 index 0000000..b7a79e4 --- /dev/null +++ b/native/Sources/BrewBrowserKit/Localization.swift @@ -0,0 +1,461 @@ +import Foundation + +/// Lightweight native localization facade. +/// +/// Static SwiftUI strings use String Catalog keys directly through this helper. +/// Model-derived strings stay structured until render time so persisted raw +/// values, package names, brew commands, and Recent Changes parsing remain +/// stable across locales. +public enum L10n { + public static func string(_ key: String) -> String { + let language = activeLanguage + if let path = Bundle.module.path(forResource: language, ofType: "lproj"), + let bundle = Bundle(path: path) { + let value = bundle.localizedString(forKey: key, value: key, table: nil) + if value != key { return value } + } + return Bundle.module.localizedString(forKey: key, value: key, table: nil) + } + + static func display(_ source: String) -> String { + if !isRussian { return source } + switch source { + case "AI & ML": return "ИИ и ML" + case "Browsers": return "Браузеры" + case "Cloud & DevOps": return "Облако и DevOps" + case "Communication": return "Коммуникации" + case "Data": return "Данные" + case "Developer Tools": return "Инструменты разработчика" + case "Editors & IDEs": return "Редакторы и IDE" + case "Education": return "Образование" + case "Games & Entertainment": return "Игры и развлечения" + case "Graphics & Design": return "Графика и дизайн" + case "Music": return "Музыка" + case "Office & Docs": return "Офис и документы" + case "Productivity": return "Продуктивность" + case "Security": return "Безопасность" + case "System Utilities": return "Системные утилиты" + case "Terminal": return "Терминал" + case "Video & Audio": return "Видео и аудио" + case "Writing": return "Работа с текстом" + case "Uncategorized": return "Без категории" + case "Other": return "Другое" + case "Formulae": return "Формулы" + case "Casks": return "Cask-пакеты" + case "formulae": return "формул" + case "casks": return "cask-пакетов" + case "Formulae (Cellar)": return "Формулы (Cellar)" + case "Casks (Caskroom)": return "Cask-пакеты (Caskroom)" + case "Logs (var/log)": return "Логи (var/log)" + case "Download cache": return "Кэш загрузок" + case "installed": return "установлено" + case "updates available": return "доступны обновления" + case "checking updates": return "проверяем обновления" + case "All current": return "Всё актуально" + case "Disabled": return "Отключено" + case "Deprecated": return "Устарело" + case "Catalog": return "Каталог" + case "Composition": return "Состав" + case "Top categories in your library": return "Главные категории в библиотеке" + case "Storage": return "Хранилище" + case "Measuring disk usage…": return "Считаем место на диске…" + case "Refresh": return "Обновить" + case "Refreshing…": return "Обновляем…" + case "Refresh from brew.sh →": return "Обновить с brew.sh →" + case "Updates available": return "Доступны обновления" + case "Choose…": return "Выбрать…" + case "Running…": return "Выполняем…" + case "Run brew doctor": return "Запустить brew doctor" + case "Cleaning…": return "Очищаем…" + case "Clean up cache…": return "Очистить кэш…" + case "Scrub — also remove the latest versions' cached downloads (more aggressive)": + return "Тщательная очистка: удалить также кэш загрузок последних версий (агрессивнее)" + case "Verbose — list every file removed": + return "Подробный вывод: перечислять каждый удалённый файл" + case "Cancel": return "Отмена" + case "Clean up": return "Очистить" + case "bundled": return "встроенный" + default: + break + } + + if let n = Int(source.removingSuffix(" on request") ?? "") { + return "\(n) вручную" + } + if let n = Int(source.removingSuffix(" as dependency") ?? "") { + return "\(n) \(ruPlural(n, one: "как зависимость", few: "как зависимости", many: "как зависимостей"))" + } + if let n = Int(source.removingSuffix(" pinned") ?? "") { + return "\(n) \(ruPlural(n, one: "закреплённый", few: "закреплённых", many: "закреплённых"))" + } + if let n = Int(source.removingSuffix(" formulae") ?? "") { + return "\(n) \(ruPlural(n, one: "формула", few: "формулы", many: "формул"))" + } + if let n = Int(source.removingSuffix(" casks") ?? "") { + return "\(n) \(ruPlural(n, one: "cask-пакет", few: "cask-пакета", many: "cask-пакетов"))" + } + if let value = source.removingSuffix(" total") { + return "всего \(value)" + } + if let value = source.removingPrefix("frees ~") { + return "освободит ~\(value)" + } + if let value = source.removingPrefix("Catalog: ") { + return "Каталог: \(value)" + } + if let value = source.removingPrefix("General ") { + return "Общее: \(display(value))" + } + if source.contains(" + ") { + return source + .split(separator: "+", maxSplits: 1) + .map { display($0.trimmingCharacters(in: .whitespaces)) } + .joined(separator: " + ") + } + return source + } + + static func formulaeCount(_ count: Int) -> String { + if isRussian { + return "\(count) \(ruPlural(count, one: "формула", few: "формулы", many: "формул"))" + } + return "\(count) formulae" + } + + static func casksCount(_ count: Int) -> String { + if isRussian { + return "\(count) \(ruPlural(count, one: "cask-пакет", few: "cask-пакета", many: "cask-пакетов"))" + } + return "\(count) casks" + } + + static func packagesCount(_ count: Int) -> String { + if isRussian { + return "\(count) \(ruPlural(count, one: "пакет", few: "пакета", many: "пакетов"))" + } + return "\(count) package\(count == 1 ? "" : "s")" + } + + static func browsePackagesByCategory(_ count: Int) -> String { + if isRussian { + return "Просматривайте \(packagesCount(count)) по категориям или используйте поиск выше." + } + return "Browse \(count) package\(count == 1 ? "" : "s") by category, or search above." + } + + static var activeLanguage: String { + let preferred = Bundle.module.preferredLocalizations.first + ?? Bundle.main.preferredLocalizations.first + ?? Locale.preferredLanguages.first + ?? Locale.autoupdatingCurrent.identifier + return preferred.lowercased().hasPrefix("ru") ? "ru" : "en" + } + + static var isRussian: Bool { activeLanguage == "ru" } + + static func ruPlural(_ n: Int, one: String, few: String, many: String) -> String { + let absN = abs(n) + let mod10 = absN % 10 + let mod100 = absN % 100 + if mod10 == 1 && mod100 != 11 { return one } + if (2...4).contains(mod10) && !(12...14).contains(mod100) { return few } + return many + } + + static func vulnerablePackages(_ count: Int) -> String { + if isRussian { + return "\(count) \(ruPlural(count, one: "уязвимый пакет", few: "уязвимых пакета", many: "уязвимых пакетов"))" + } + return "\(count) vulnerable package\(count == 1 ? "" : "s")" + } + + static func vulnerablePackagesHelp(_ count: Int) -> String { + if isRussian { + let packages = ruPlural(count, one: "установленный пакет содержит", few: "установленных пакета содержат", many: "установленных пакетов содержат") + return "\(count) \(packages) известные уязвимости. Нажмите, чтобы открыть их в Библиотеке." + } + return "\(count) installed package\(count == 1 ? " has" : "s have") known vulnerabilities. Click to view them in Library." + } + + static func brewOperationsRunning(_ count: Int) -> String { + if isRussian { + return "\(count) \(ruPlural(count, one: "операция brew выполняется", few: "операции brew выполняются", many: "операций brew выполняется"))" + } + return "\(count) brew operation\(count == 1 ? "" : "s") running" + } + + static func githubAction(_ action: String) -> String { + if !isRussian { return action } + switch action { + case "Star": return "Звезда" + case "Watch": return "Отслеживание" + default: return action + } + } + + static func servicesSummary(running: Int, total: Int) -> String { + if isRussian { + let runningWord = ruPlural(running, one: "запущена", few: "запущены", many: "запущено") + return "\(running) \(runningWord) · всего \(total)" + } + return "\(running) running · \(total) total" + } + + static func selectedCount(_ selected: Int, total: Int) -> String { + if isRussian { + return "Выбрано \(selected) из \(total)" + } + return "\(selected) of \(total) selected" + } + + static func catalogAge(days: Int) -> String { + if days <= 0 { return string("date.today") } + if isRussian { + return "\(days) \(ruPlural(days, one: "день", few: "дня", many: "дней"))" + } + if days == 1 { return "1 day old" } + return "\(days) days old" + } + + static func catalogStaleBanner(age: String) -> String { + if isRussian { + let when = age == string("date.today") ? "сегодня" : "\(age) назад" + return "Каталог обновлялся \(when). Новые пакеты и сведения об устаревании могут отсутствовать." + } + return "Catalog is \(age). Newer packages and deprecations may be missing." + } + + static func shortElapsed(seconds: Int) -> String { + if isRussian { + if seconds < 60 { return "\(seconds) с" } + if seconds < 3600 { return "\(seconds / 60) мин." } + return "\(seconds / 3600) ч" + } + if seconds < 60 { return "\(seconds)s" } + if seconds < 3600 { return "\(seconds / 60)m" } + return "\(seconds / 3600)h" + } + + static func upgradePackageButton(_ count: Int) -> String { + if count == 0 { return string("action.upgrade") } + if isRussian { + return "Обновить \(count) \(ruPlural(count, one: "пакет", few: "пакета", many: "пакетов"))" + } + return "Upgrade \(count) package\(count == 1 ? "" : "s")" + } + + static func exposureSummary(findings: Int, vulnerablePackages: Int, totalPackages: Int, source: String?) -> String { + let sourceSuffix: String + if let source { + sourceSuffix = isRussian ? " · источник: \(source)" : " · source: \(source)" + } else { + sourceSuffix = "" + } + if isRussian { + let findingWord = ruPlural(findings, one: "запись", few: "записи", many: "записей") + let packageWord = ruPlural(totalPackages, one: "установленного пакета", few: "установленных пакетов", many: "установленных пакетов") + return "\(findings) \(findingWord) в \(vulnerablePackages) из \(totalPackages) \(packageWord)\(sourceSuffix)" + } + return "\(findings) finding\(findings == 1 ? "" : "s") across \(vulnerablePackages) of \(totalPackages) installed packages\(sourceSuffix)" + } + + static func severity(_ severity: VulnSeverity) -> String { + switch severity { + case .critical: return isRussian ? "критическая" : "critical" + case .high: return isRussian ? "высокая" : "high" + case .medium: return isRussian ? "средняя" : "medium" + case .low: return isRussian ? "низкая" : "low" + case .unknown: return isRussian ? "неизвестная" : "unknown" + } + } + + static func severityTitle(_ severity: VulnSeverity) -> String { + if !isRussian { return severity.rawValue.capitalized } + switch severity { + case .critical: return "Критическая" + case .high: return "Высокая" + case .medium: return "Средняя" + case .low: return "Низкая" + case .unknown: return "Неизвестная" + } + } + + static func vulnerabilityDotHelp(count: Int, severity: VulnSeverity) -> String { + if isRussian { + let findingWord = ruPlural(count, one: "известная уязвимость", few: "известные уязвимости", many: "известных уязвимостей") + return "\(count) \(findingWord) (наивысшая серьёзность: \(self.severity(severity))). Нажмите строку, чтобы посмотреть детали." + } + return "\(count) known vulnerabilit\(count == 1 ? "y" : "ies") (highest: \(severity.rawValue)). Click row to see details." + } + + static func githubCheckingStarred(total: Int) -> String { + if total <= 0 { return string("github.checkingStarred") } + if isRussian { + let packageWord = ruPlural(total, one: "пакета", few: "пакетов", many: "пакетов") + return "Проверяем, какие из \(total) \(packageWord) вы отметили звёздочкой..." + } + return "Checking which of your \(total) packages you've starred..." + } + + static func githubStarredSummary(starred: Int, total: Int) -> String { + if isRussian { + let packageWord = ruPlural(total, one: "установленного пакета", few: "установленных пакетов", many: "установленных пакетов") + return "Вы отметили звёздочкой \(starred) из \(total) \(packageWord) с домашней страницей на GitHub." + } + return "You've starred \(starred) of \(total) installed packages with GitHub homepages." + } + + static func moreInLibrary(_ count: Int) -> String { + if isRussian { + return "+ ещё \(count) в Библиотеке" + } + return "+ \(count) more in Library" + } + + static func activityDisplayLabel(_ job: ActivityJob) -> String { + if !isRussian { return englishActivityDisplayLabel(job) } + let running = russianActivityLabel(job.label) + switch job.status { + case .running: + return running + case .succeeded: + return russianCompletedActivityLabel(job.label) ?? running + case .failed: + return "Ошибка: \(running)" + case .canceled: + return "Отменено: \(running)" + } + } + + static func activityNotificationBody(label: String, succeeded: Bool) -> String { + if !isRussian { + return succeeded ? englishCompletedActivityLabel(label) : "Failed: \(label)" + } + if succeeded { + return russianCompletedActivityLabel(label) ?? russianActivityLabel(label) + } + return "Ошибка: \(russianActivityLabel(label))" + } + + static func activityFailureTitle(for label: String) -> String { + if !isRussian { + if label.hasPrefix("Upgrading ") { return "Upgrade failed" } + if label.hasPrefix("Installing ") { return "Install failed" } + if label.hasPrefix("Uninstalling ") { return "Uninstall failed" } + return "\(label) failed" + } + if label.hasPrefix("Upgrading ") { return "Не удалось обновить" } + if label.hasPrefix("Installing ") { return "Не удалось установить" } + if label.hasPrefix("Uninstalling ") { return "Не удалось удалить" } + if label.hasPrefix("Adopting ") { return "Не удалось взять под управление" } + if label.hasPrefix("Reinstalling ") { return "Не удалось переустановить" } + if label.hasPrefix("Force-removing ") { return "Не удалось принудительно удалить" } + if label.hasPrefix("Start ") { return "Не удалось запустить службу" } + if label.hasPrefix("Stop ") { return "Не удалось остановить службу" } + if label.hasPrefix("Restart ") { return "Не удалось перезапустить службу" } + return "Не удалось выполнить: \(russianActivityLabel(label))" + } + + static func progressLabel(_ p: JobProgress) -> String { + if !isRussian { + var s = p.phase + if !p.package.isEmpty { s += " \(p.package)" } + if let total = p.total { s += " (\(p.current) of \(total))" } + return s + } + let phase: String + switch p.phase { + case "Downloading": phase = "Загрузка" + case "Fetching": phase = "Загрузка" + case "Pouring": phase = "Распаковка" + case "Installing": phase = "Установка" + case "Upgrading": phase = "Обновление" + default: phase = p.phase + } + var s = phase + if !p.package.isEmpty { s += " \(p.package)" } + if let total = p.total { s += " (\(p.current) из \(total))" } + return s + } + + static func packageKind(_ kind: InstalledPackage.Kind) -> String { + if !isRussian { return kind.rawValue } + return kind == .formula ? "формула" : "cask-пакет" + } + + private static func englishCompletedActivityLabel(_ label: String) -> String { + label + .replacingOccurrences(of: "Installing ", with: "Installed ") + .replacingOccurrences(of: "Upgrading ", with: "Upgraded ") + .replacingOccurrences(of: "Uninstalling ", with: "Uninstalled ") + } + + private static func englishActivityDisplayLabel(_ job: ActivityJob) -> String { + switch job.status { + case .running: return job.label + case .succeeded: return englishCompletedActivityLabel(job.label) + case .failed: return "Failed: \(job.label)" + case .canceled: return "Canceled: \(job.label)" + } + } + + private static func russianActivityLabel(_ label: String) -> String { + if label == "Updating Homebrew" { return "Обновляем Homebrew" } + if label == "Running brew doctor" { return "Выполняем brew doctor" } + if label == "Cleaning up Homebrew cache" { return "Очищаем кэш Homebrew" } + if label == "Upgrading all packages" { return "Обновляем все пакеты" } + if label == "Removing unused dependencies" { return "Удаляем неиспользуемые зависимости" } + if let value = label.removingPrefix("Upgrading "), + let countText = value.removingSuffix(" packages"), + let count = Int(countText) { + return "Обновляем \(count) \(ruPlural(count, one: "пакет", few: "пакета", many: "пакетов"))" + } + if let name = label.removingPrefix("Installing ") { return "Устанавливаем \(name)" } + if let name = label.removingPrefix("Upgrading ") { return "Обновляем \(name)" } + if let name = label.removingPrefix("Uninstalling ") { return "Удаляем \(name)" } + if let name = label.removingPrefix("Adopting ") { return "Берём под управление \(name)" } + if let name = label.removingPrefix("Reinstalling ") { return "Переустанавливаем \(name)" } + if let name = label.removingPrefix("Force-removing ") { return "Принудительно удаляем \(name)" } + if let name = label.removingPrefix("Start ") { return "Запускаем службу \(name)" } + if let name = label.removingPrefix("Stop ") { return "Останавливаем службу \(name)" } + if let name = label.removingPrefix("Restart ") { return "Перезапускаем службу \(name)" } + if let label = label.removingPrefix("Dumping Brewfile: ") { return "Создаём Brewfile: \(label)" } + if let label = label.removingPrefix("Restoring ") { return "Восстанавливаем \(label)" } + return label + } + + private static func russianCompletedActivityLabel(_ label: String) -> String? { + if label == "Updating Homebrew" { return "Homebrew обновлён" } + if label == "Running brew doctor" { return "brew doctor завершён" } + if label == "Cleaning up Homebrew cache" { return "Кэш Homebrew очищен" } + if label == "Upgrading all packages" { return "Все пакеты обновлены" } + if label == "Removing unused dependencies" { return "Неиспользуемые зависимости удалены" } + if let value = label.removingPrefix("Upgrading "), + let countText = value.removingSuffix(" packages"), + let count = Int(countText) { + return "Обновлено \(count) \(ruPlural(count, one: "пакет", few: "пакета", many: "пакетов"))" + } + if let name = label.removingPrefix("Installing ") { return "Установлено \(name)" } + if let name = label.removingPrefix("Upgrading ") { return "Обновлено \(name)" } + if let name = label.removingPrefix("Uninstalling ") { return "Удалено \(name)" } + if let name = label.removingPrefix("Adopting ") { return "Взято под управление \(name)" } + if let name = label.removingPrefix("Reinstalling ") { return "Переустановлено \(name)" } + if let name = label.removingPrefix("Force-removing ") { return "Принудительно удалено \(name)" } + if let name = label.removingPrefix("Start ") { return "Служба \(name) запущена" } + if let name = label.removingPrefix("Stop ") { return "Служба \(name) остановлена" } + if let name = label.removingPrefix("Restart ") { return "Служба \(name) перезапущена" } + if let label = label.removingPrefix("Dumping Brewfile: ") { return "Brewfile создан: \(label)" } + if let label = label.removingPrefix("Restoring ") { return "Восстановлено \(label)" } + return nil + } +} + +private extension String { + func removingPrefix(_ prefix: String) -> String? { + hasPrefix(prefix) ? String(dropFirst(prefix.count)) : nil + } + + func removingSuffix(_ suffix: String) -> String? { + hasSuffix(suffix) ? String(dropLast(suffix.count)) : nil + } +} diff --git a/native/Sources/BrewBrowserKit/NotificationService.swift b/native/Sources/BrewBrowserKit/NotificationService.swift index 89d31a9..1f660a5 100644 --- a/native/Sources/BrewBrowserKit/NotificationService.swift +++ b/native/Sources/BrewBrowserKit/NotificationService.swift @@ -36,8 +36,10 @@ enum NotificationService { UNUserNotificationCenter.current().getNotificationSettings { settings in guard settings.authorizationStatus == .authorized else { return } let content = UNMutableNotificationContent() - content.title = succeeded ? "Homebrew task finished" : "Homebrew task failed" - content.body = label + content.title = succeeded + ? (L10n.isRussian ? "Задача Homebrew завершена" : "Homebrew task finished") + : (L10n.isRussian ? "Задача Homebrew завершилась ошибкой" : "Homebrew task failed") + content.body = L10n.activityNotificationBody(label: label, succeeded: succeeded) if !succeeded { content.sound = .default } let request = UNNotificationRequest( identifier: UUID().uuidString, content: content, trigger: nil diff --git a/native/Sources/BrewBrowserKit/OnboardingView.swift b/native/Sources/BrewBrowserKit/OnboardingView.swift index 49ed368..84e9aae 100644 --- a/native/Sources/BrewBrowserKit/OnboardingView.swift +++ b/native/Sources/BrewBrowserKit/OnboardingView.swift @@ -33,10 +33,10 @@ struct OnboardingView: View { Image(systemName: "shippingbox") .font(.system(size: 44)) .foregroundStyle(.secondary) - Text("Homebrew isn't installed") + Text(L10n.string("Homebrew isn't installed")) .font(.title2) .fontWeight(.semibold) - Text("brew-browser is a window onto Homebrew — install it and the app takes it from there.") + Text(L10n.string("brew-browser is a window onto Homebrew — install it and the app takes it from there.")) .foregroundStyle(.secondary) .multilineTextAlignment(.center) } @@ -45,13 +45,13 @@ struct OnboardingView: View { // Step: Command Line Tools, only when the probe says missing — // the Homebrew installer needs them, so they come first. if !model.cltInstalled { - step(number: 1, title: "Install the Xcode Command Line Tools", - detail: "The Homebrew installer needs them. Run this in Terminal and follow the prompt:", + step(number: 1, title: L10n.string("Install the Xcode Command Line Tools"), + detail: L10n.string("The Homebrew installer needs them. Run this in Terminal and follow the prompt:"), command: Self.cltCommand) } - step(number: model.cltInstalled ? 1 : 2, title: "Install Homebrew", - detail: "Paste the official install command (from brew.sh) into Terminal:", + step(number: model.cltInstalled ? 1 : 2, title: L10n.string("Install Homebrew"), + detail: L10n.string("Paste the official install command (from brew.sh) into Terminal:"), command: Self.installCommand) HStack { @@ -59,7 +59,7 @@ struct OnboardingView: View { Button { openTerminal() } label: { - Label("Open Terminal", systemImage: "terminal") + Label(L10n.string("Open Terminal"), systemImage: "terminal") } Spacer() } @@ -71,7 +71,7 @@ struct OnboardingView: View { HStack(spacing: 8) { ProgressView() .controlSize(.small) - Text("Waiting for Homebrew… the app continues automatically once it's installed.") + Text(L10n.string("Waiting for Homebrew… the app continues automatically once it's installed.")) .font(.footnote) .foregroundStyle(.secondary) } @@ -105,9 +105,9 @@ struct OnboardingView: View { Button { copyToPasteboard(command) } label: { - Label("Copy", systemImage: "doc.on.doc") + Label(L10n.string("Copy"), systemImage: "doc.on.doc") } - .help("Copy the command to the clipboard") + .help(L10n.string("Copy the command to the clipboard")) } .padding(4) } diff --git a/native/Sources/BrewBrowserKit/PackageDetailView.swift b/native/Sources/BrewBrowserKit/PackageDetailView.swift index 3a953c5..8abe7fa 100644 --- a/native/Sources/BrewBrowserKit/PackageDetailView.swift +++ b/native/Sources/BrewBrowserKit/PackageDetailView.swift @@ -55,7 +55,7 @@ struct PackageDetailView: View { .font(.title3) } .buttonStyle(.plain) - .help("Close") + .help(L10n.string("Close")) } .padding(.bottom, -8) // tuck the title block up under the ✕ row @@ -73,10 +73,10 @@ struct PackageDetailView: View { .frame(maxWidth: .infinity) if model.detailLoading && info == nil { - ProgressView("Loading \(pkg.name)…") + ProgressView(L10n.isRussian ? "Загружаем \(pkg.name)…" : "Loading \(pkg.name)…") .frame(maxWidth: .infinity, minHeight: 200) } else if let err = model.detailError, info == nil { - ContentUnavailableView("Couldn't load \(pkg.name)", + ContentUnavailableView(L10n.isRussian ? "Не удалось загрузить \(pkg.name)" : "Couldn't load \(pkg.name)", systemImage: "exclamationmark.triangle", description: Text(err)) } else { @@ -138,16 +138,16 @@ struct PackageDetailView: View { private func deviceFlowSheet(_ flow: DeviceFlowStart) -> some View { VStack(spacing: 16) { Image(systemName: "person.badge.key").font(.largeTitle).foregroundStyle(.secondary) - Text("Sign in to GitHub").font(.title2.weight(.semibold)) - Text("Your browser opened to GitHub's device page. Enter this code (it's copied to your clipboard):") + Text(L10n.string("Sign in to GitHub")).font(.title2.weight(.semibold)) + Text(L10n.string("Your browser opened to GitHub's device page. Enter this code (it's copied to your clipboard):")) .font(.callout).foregroundStyle(.secondary).multilineTextAlignment(.center) Text(flow.userCode) .font(.system(.title, design: .monospaced).weight(.bold)) .textSelection(.enabled) .padding(.horizontal, 16).padding(.vertical, 8) .background(.quaternary, in: .rect(cornerRadius: 8)) - ProgressView("Waiting for authorization…").controlSize(.small) - Button("Cancel") { model.cancelGitHubSignIn() } + ProgressView(L10n.string("Waiting for authorization…")).controlSize(.small) + Button(L10n.string("Cancel")) { model.cancelGitHubSignIn() } if let err = model.githubSignInError { Text(err).font(.caption).foregroundStyle(.red) } @@ -158,14 +158,14 @@ struct PackageDetailView: View { private var issueSheet: some View { VStack(alignment: .leading, spacing: 12) { - Text("File an issue for \(pkg.name)").font(.headline) - TextField("Title", text: $issueTitle) - TextField("Description", text: $issueBody, axis: .vertical) + Text(L10n.isRussian ? "Сообщить о проблеме для \(pkg.name)" : "File an issue for \(pkg.name)").font(.headline) + TextField(L10n.string("Title"), text: $issueTitle) + TextField(L10n.string("Description"), text: $issueBody, axis: .vertical) .lineLimit(5...10) HStack { Spacer() - Button("Cancel") { showIssueSheet = false } - Button("Submit") { + Button(L10n.string("Cancel")) { showIssueSheet = false } + Button(L10n.string("Submit")) { let t = issueTitle, b = issueBody showIssueSheet = false Task { @@ -186,50 +186,50 @@ struct PackageDetailView: View { private var metaCard: some View { VStack(spacing: 0) { - metaRow("Token", pkg.name, mono: true) + metaRow(L10n.string("Token"), pkg.name, mono: true) Divider() HStack { - Text("Installed").foregroundStyle(.secondary) + Text(L10n.string("Installed")).foregroundStyle(.secondary) .frame(width: 90, alignment: .leading) - Text(info?.installedVersion ?? "Not installed") + Text(info?.installedVersion ?? L10n.string("Not installed")) .textSelection(.enabled) // Dependency indicator: a formula installed only as a dependency // (not on request) — mirrors the Tauri detail badge. Casks are // always on-request so never show this. Feature #3. if isInstalled, pkg.kind == .formula, !installedOnRequest { - Text("Dependency") + Text(L10n.string("Dependency")) .font(.caption) .foregroundStyle(.secondary) .padding(.horizontal, 6).padding(.vertical, 2) .background(Color.secondary.opacity(0.12), in: .capsule) - .help("Installed as a dependency, not requested directly") + .help(L10n.string("Installed as a dependency, not requested directly")) } Spacer() } .padding(.vertical, 6) Divider() HStack { - Text("Latest").foregroundStyle(.secondary) + Text(L10n.string("Latest")).foregroundStyle(.secondary) .frame(width: 90, alignment: .leading) Text(info?.stableVersion ?? "—").monospaced() if info?.isOutdated == true { - Text("Upgrade available") + Text(L10n.string("Upgrade available")) .font(.caption).foregroundStyle(.orange) } Spacer() } .padding(.vertical, 6) if let lic = info?.license { - Divider(); metaRow("License", lic) + Divider(); metaRow(L10n.string("License"), lic) } if let tap = info?.tap { - Divider(); metaRow("Tap", tap, mono: true) + Divider(); metaRow(L10n.string("Tap"), tap, mono: true) } // On-disk size of the installed keg (feature #4). Rendered only when // the package is installed and du succeeded — nil → no row (never a // fabricated "0 B"). Lazily computed in BrewService.info(). if let bytes = info?.installedSizeBytes { - Divider(); metaRow("Size", Self.human(bytes)) + Divider(); metaRow(L10n.string("Size"), Self.human(bytes)) } } .padding(12) @@ -261,12 +261,12 @@ struct PackageDetailView: View { } private var categoriesRow: some View { - VStack(alignment: .leading, spacing: 4) { - FlowRow(spacing: 6) { - ForEach(model.detailCategories, id: \.self) { label in - Chip(text: label) - } - } + VStack(alignment: .leading, spacing: 4) { + FlowRow(spacing: 6) { + ForEach(model.detailCategories, id: \.self) { label in + Chip(text: L10n.display(label)) + } + } HStack { Spacer() wrongButton(.categories, currentValue: model.detailCategories.joined(separator: ", ")) @@ -326,9 +326,7 @@ struct PackageDetailView: View { let tone: Color = isDisabled ? .red : .orange GroupBox { VStack(alignment: .leading, spacing: 8) { - Text(isDisabled - ? "This package is disabled and is no longer available — it will be removed." - : "This package is deprecated and scheduled for removal. It still installs for now.") + Text(L10n.string(isDisabled ? "detail.deprecation.disabled.body" : "detail.deprecation.deprecated.body")) .font(.callout) .frame(maxWidth: .infinity, alignment: .leading) if let reason = status.activeReason, !reason.isEmpty { @@ -338,12 +336,12 @@ struct PackageDetailView: View { .textSelection(.enabled) } if let date = status.activeDate, !date.isEmpty { - Text(isDisabled ? "Disabled: \(date)" : "Deprecated: \(date)") + Text(String(format: L10n.string(isDisabled ? "detail.deprecation.disabledDate.format" : "detail.deprecation.deprecatedDate.format"), date)) .font(.caption).foregroundStyle(.secondary) } if let replacement = status.activeReplacement, !replacement.isEmpty { HStack(spacing: 6) { - Text("Use instead:").font(.callout).foregroundStyle(.secondary) + Text(L10n.string("detail.deprecation.useInstead")).font(.callout).foregroundStyle(.secondary) Button { // Most replacements share the deprecated package's // kind (formula→formula, cask→cask); openDetail @@ -372,27 +370,27 @@ struct PackageDetailView: View { GroupBox { VStack(alignment: .leading, spacing: 10) { if !model.brewVulnsInstalled { - Text("Install `brew vulns` to scan this package for known vulnerabilities.") + Text(L10n.string("Install `brew vulns` to scan this package for known vulnerabilities.")) .font(.callout).foregroundStyle(.secondary) } else if model.detailVulnsLoading { - ProgressView("Scanning all packages…") + ProgressView(L10n.string("Scanning all packages…")) } else if !model.detailVulnsScanned { // Never scanned — hazard tone; don't imply safety unverified. Label { - Text("Not scanned yet. Scanning checks **every** installed package — `brew vulns` can't scan just one.") + Text(L10n.string("detail.security.notScanned.detail")) .font(.callout).foregroundStyle(.secondary) } icon: { Image(systemName: "exclamationmark.triangle.fill").foregroundStyle(.orange) } - Button("Scan all packages") { Task { await model.scanDetailVulns() } } + Button(L10n.string("Scan all packages")) { Task { await model.scanDetailVulns() } } } else if model.detailVulns.isEmpty { // "Clean" is only a confident green when freshly scanned this // session; a cached/stale clean is a caution, not an all-clear. if model.vulnScannedThisSession { - Label("No known vulnerabilities", systemImage: "checkmark.seal.fill") + Label(L10n.string("No known vulnerabilities"), systemImage: "checkmark.seal.fill") .foregroundStyle(.green) } else { - Label("No advisories as of the last scan — re-scan to confirm", systemImage: "exclamationmark.triangle.fill") + Label(L10n.string("detail.security.cleanStale"), systemImage: "exclamationmark.triangle.fill") .foregroundStyle(.orange) } rescanRow @@ -416,15 +414,15 @@ struct PackageDetailView: View { } } .buttonStyle(.link) - .help("Open advisory: \(url.absoluteString)") + .help(String(format: L10n.string("detail.security.openAdvisory.format"), url.absoluteString)) } else { - Text(v.rawId.isEmpty ? "Advisory" : v.rawId) + Text(v.rawId.isEmpty ? L10n.string("Advisory") : v.rawId) .font(.callout.monospaced()) } SeverityPill(severity: v.severity) Spacer() if let fixed = v.fixedIn { - Text("Patched in \(fixed)").font(.caption).foregroundStyle(.secondary) + Text(String(format: L10n.string("detail.security.patchedIn.format"), fixed)).font(.caption).foregroundStyle(.secondary) } } if !v.summary.isEmpty { @@ -439,7 +437,7 @@ struct PackageDetailView: View { .frame(maxWidth: .infinity, alignment: .leading) .padding(.top, 2) } label: { - Label("Security", systemImage: "shield") + Label(L10n.string("Security"), systemImage: "shield") } } @@ -448,16 +446,16 @@ struct PackageDetailView: View { /// makes that explicit. @ViewBuilder private var rescanRow: some View { HStack(spacing: 8) { - Button("Re-scan all") { Task { await model.scanDetailVulns() } } + Button(L10n.string("Re-scan all")) { Task { await model.scanDetailVulns() } } .controlSize(.small) if let at = model.vulnLastScannedAt { - Text("Last scan: \(at.formatted(.relative(presentation: .named)))") + Text(String(format: L10n.string("detail.security.lastScan.format"), at.formatted(.relative(presentation: .named)))) .font(.caption2).foregroundStyle(.secondary) } Spacer() } .padding(.top, 2) - .help("Runs brew vulns across your whole install — it can't scan a single package") + .help(L10n.string("detail.security.rescanHelp")) } // MARK: service @@ -473,18 +471,18 @@ struct PackageDetailView: View { ServiceStatusPill(status: svc.status) Spacer() if let user = svc.user { - Text("user: \(user)").font(.caption).foregroundStyle(.secondary) + Text(L10n.isRussian ? "пользователь: \(user)" : "user: \(user)").font(.caption).foregroundStyle(.secondary) } } if model.servicePending.contains(svc.name) { ProgressView().controlSize(.small) } else { HStack(spacing: 8) { - Button("Start") { Task { await model.performServiceAction(.start, name: svc.name) } } + Button(L10n.string("Start")) { Task { await model.performServiceAction(.start, name: svc.name) } } .disabled(svc.status == .started) - Button("Stop") { Task { await model.performServiceAction(.stop, name: svc.name) } } + Button(L10n.string("Stop")) { Task { await model.performServiceAction(.stop, name: svc.name) } } .disabled(svc.status == .stopped || svc.status == .notLoaded) - Button("Restart") { Task { await model.performServiceAction(.restart, name: svc.name) } } + Button(L10n.string("Restart")) { Task { await model.performServiceAction(.restart, name: svc.name) } } } .controlSize(.small) } @@ -492,7 +490,7 @@ struct PackageDetailView: View { .frame(maxWidth: .infinity, alignment: .leading) .padding(.top, 2) } label: { - Label("Service", systemImage: "gearshape.2") + Label(L10n.string("Service"), systemImage: "gearshape.2") } } } @@ -513,10 +511,10 @@ struct PackageDetailView: View { .chartYAxis(.hidden) .padding(.top, 4) } else { - Text("Not enough data yet").font(.caption).foregroundStyle(.secondary) + Text(L10n.string("Not enough data yet")).font(.caption).foregroundStyle(.secondary) } } label: { - Label("Install trend", systemImage: "chart.line.uptrend.xyaxis") + Label(L10n.string("Install trend"), systemImage: "chart.line.uptrend.xyaxis") } } } @@ -543,7 +541,7 @@ struct PackageDetailView: View { .frame(maxWidth: .infinity, alignment: .leading) .padding(.top, 2) } label: { - Label("Why install this?", systemImage: "lightbulb") + Label(L10n.string("Why install this?"), systemImage: "lightbulb") } } @@ -566,7 +564,7 @@ struct PackageDetailView: View { .frame(maxWidth: .infinity, alignment: .leading) .padding(.top, 2) } label: { - Label("Similar packages", systemImage: "square.stack.3d.up") + Label(L10n.string("Similar packages"), systemImage: "square.stack.3d.up") } } @@ -597,7 +595,9 @@ struct PackageDetailView: View { // Archived-repo warning — likely unmaintained. if stats.archived { Label { - Text("Archived\(stats.archivedAt.map { " \(relativeISO($0))" } ?? "") — likely unmaintained.") + Text(L10n.isRussian + ? "Архивирован\(stats.archivedAt.map { " \(relativeISO($0))" } ?? "") — вероятно, больше не поддерживается." + : "Archived\(stats.archivedAt.map { " \(relativeISO($0))" } ?? "") — likely unmaintained.") } icon: { Image(systemName: "archivebox").foregroundStyle(.orange) } @@ -608,13 +608,17 @@ struct PackageDetailView: View { // License-mismatch warning — brew license != GitHub SPDX. if let spdx = stats.licenseSpdx, let brewLic = info?.license, spdx != brewLic { Label { - Text("License mismatch — brew: \(Text(brewLic).monospaced()), GitHub: \(Text(spdx).monospaced())") + if L10n.isRussian { + Text("Лицензии не совпадают — brew: \(Text(brewLic).monospaced()), GitHub: \(Text(spdx).monospaced())") + } else { + Text("License mismatch — brew: \(Text(brewLic).monospaced()), GitHub: \(Text(spdx).monospaced())") + } } icon: { Image(systemName: "exclamationmark.triangle").foregroundStyle(.orange) } .font(.caption) .foregroundStyle(.secondary) - .help("brew reports: \(brewLic) · GitHub reports: \(spdx)") + .help(L10n.isRussian ? "brew сообщает: \(brewLic) · GitHub сообщает: \(spdx)" : "brew reports: \(brewLic) · GitHub reports: \(spdx)") } // Star / Watch / File issue — each routes through the model, @@ -626,32 +630,32 @@ struct PackageDetailView: View { Button { Task { await model.toggleStar() } } label: { - Label(starred ? "Starred" : "Star", + Label(starred ? L10n.string("Starred") : L10n.string("Star"), systemImage: starred ? "star.fill" : "star") } .help(!model.githubSignedIn - ? "Sign in to GitHub to star this repository" - : starred ? "Unstar this repository" : "Star this repository") + ? L10n.string("Sign in to GitHub to star this repository") + : starred ? L10n.string("Unstar this repository") : L10n.string("Star this repository")) Button { Task { await model.toggleWatch() } } label: { - Label(watching ? "Watching" : "Watch", + Label(watching ? L10n.string("Watching") : L10n.string("Watch"), systemImage: watching ? "eye.fill" : "eye") } .help(!model.githubSignedIn - ? "Sign in to GitHub to watch this repository" - : watching ? "Stop watching" : "Watch for activity") + ? L10n.string("Sign in to GitHub to watch this repository") + : watching ? L10n.string("Stop watching") : L10n.string("Watch for activity")) Button { showIssueSheet = true } label: { - Label("File issue", systemImage: "exclamationmark.bubble") + Label(L10n.string("File issue"), systemImage: "exclamationmark.bubble") } Spacer() } .controlSize(.small) if !model.githubSignedIn { - Text("Sign in to GitHub happens on first action.") + Text(L10n.string("Sign in to GitHub happens on first action.")) .font(.caption).foregroundStyle(.secondary) } } @@ -681,20 +685,20 @@ struct PackageDetailView: View { .frame(maxWidth: .infinity, alignment: .leading) .padding(.top, 2) } label: { - Label("Caveats", systemImage: "exclamationmark.bubble") + Label(L10n.string("Caveats"), systemImage: "exclamationmark.bubble") } } @ViewBuilder private var dependenciesSection: some View { if let info, !info.dependencies.isEmpty { - DisclosureGroup("Dependencies (\(info.dependencies.count))") { + DisclosureGroup(L10n.isRussian ? "Зависимости (\(info.dependencies.count))" : "Dependencies (\(info.dependencies.count))") { FlowRow(spacing: 6) { ForEach(info.dependencies, id: \.self) { Chip(text: $0) } } } } if let info, !info.conflictsWith.isEmpty { - DisclosureGroup("Conflicts with (\(info.conflictsWith.count))") { + DisclosureGroup(L10n.isRussian ? "Конфликтует с (\(info.conflictsWith.count))" : "Conflicts with (\(info.conflictsWith.count))") { FlowRow(spacing: 6) { ForEach(info.conflictsWith, id: \.self) { Chip(text: $0) } } @@ -712,7 +716,7 @@ struct PackageDetailView: View { @ViewBuilder private var dependentsSection: some View { let dependents = model.detailDependents if !dependents.isEmpty { - DisclosureGroup("Required by (\(dependents.count))") { + DisclosureGroup(L10n.isRussian ? "Зависимые пакеты (\(dependents.count))" : "Required by (\(dependents.count))") { FlowRow(spacing: 6) { ForEach(dependents) { dep in Button { @@ -721,7 +725,7 @@ struct PackageDetailView: View { HStack(spacing: 4) { Text(dep.name) if dep.edge != .required { - Text(dep.edge.rawValue) + Text(edgeLabel(dep.edge)) .font(.caption2) .foregroundStyle(.secondary) } else if dep.kind == .cask { @@ -738,13 +742,23 @@ struct PackageDetailView: View { } } + private func edgeLabel(_ edge: ReverseDependent.Edge) -> String { + if !L10n.isRussian { return edge.rawValue } + switch edge { + case .required: return "обязательная" + case .recommended: return "рекомендуемая" + case .build: return "для сборки" + case .optional: return "опциональная" + } + } + // MARK: footer private var footer: some View { HStack { if model.actionRunning { ProgressView().controlSize(.small) - Text("Working… (see Activity)").font(.caption).foregroundStyle(.secondary) + Text(L10n.string("Working… (see Activity)")).font(.caption).foregroundStyle(.secondary) } Spacer() // Footer adapts to install state, driven by the live installed list @@ -757,18 +771,18 @@ struct PackageDetailView: View { if info?.isOutdated == true { Button { Task { await model.upgradeDetail(greedy: LocalPrefs.shared.greedyUpgrade) } - } label: { Label("Upgrade", systemImage: "arrow.up.circle") } + } label: { Label(L10n.string("Upgrade"), systemImage: "arrow.up.circle") } .buttonStyle(.borderedProminent) .disabled(model.actionRunning) } Button(role: .destructive) { confirmUninstall = true - } label: { Label("Uninstall", systemImage: "trash") } + } label: { Label(L10n.string("Uninstall"), systemImage: "trash") } .disabled(model.actionRunning) } else { Button { Task { await model.installDetail() } - } label: { Label("Install", systemImage: "arrow.down.circle") } + } label: { Label(L10n.string("Install"), systemImage: "arrow.down.circle") } .buttonStyle(.borderedProminent) .disabled(model.actionRunning) } @@ -776,11 +790,11 @@ struct PackageDetailView: View { } .padding(12) .background(.bar) - .confirmationDialog("Uninstall \(pkg.name)?", isPresented: $confirmUninstall, titleVisibility: .visible) { - Button("Uninstall", role: .destructive) { Task { await model.uninstallDetail() } } - Button("Cancel", role: .cancel) {} + .confirmationDialog(L10n.isRussian ? "Удалить \(pkg.name)?" : "Uninstall \(pkg.name)?", isPresented: $confirmUninstall, titleVisibility: .visible) { + Button(L10n.string("Uninstall"), role: .destructive) { Task { await model.uninstallDetail() } } + Button(L10n.string("Cancel"), role: .cancel) {} } message: { - Text("This runs `brew uninstall \(pkg.name)`.") + Text(L10n.isRussian ? "Будет выполнено `brew uninstall \(pkg.name)`." : "This runs `brew uninstall \(pkg.name)`.") } } } @@ -797,7 +811,7 @@ struct SeverityPill: View { } } var body: some View { - Text(severity.rawValue.capitalized) + Text(L10n.severityTitle(severity)) .font(.caption2.weight(.medium)) .padding(.horizontal, 7).padding(.vertical, 2) .background(color.opacity(0.2), in: .capsule) @@ -826,7 +840,7 @@ struct SeverityDot: View { Circle() .fill(color) .frame(width: 8, height: 8) - .help("\(count) known vulnerabilit\(count == 1 ? "y" : "ies") (highest: \(severity.rawValue)). Click row to see details.") + .help(L10n.vulnerabilityDotHelp(count: count, severity: severity)) } } @@ -846,7 +860,7 @@ struct DeprecationBadge: View { private var color: Color { kind == .disabled ? .red : .orange } var body: some View { - Text(kind.label) + Text(L10n.display(kind.label)) .font(.caption2.weight(.medium)) .padding(.horizontal, 6).padding(.vertical, 1) .background(color.opacity(0.18), in: .capsule) @@ -856,8 +870,8 @@ struct DeprecationBadge: View { private var tooltip: String { let base = kind == .disabled - ? "Disabled — this package is no longer available and will be removed." - : "Deprecated — still installable, but scheduled for removal." + ? L10n.string("detail.deprecation.disabled.tooltip") + : L10n.string("detail.deprecation.deprecated.tooltip") if let reason, !reason.isEmpty { return "\(base)\n\(reason)" } return base } diff --git a/native/Sources/BrewBrowserKit/ReportIssue.swift b/native/Sources/BrewBrowserKit/ReportIssue.swift index d342395..3bf53a3 100644 --- a/native/Sources/BrewBrowserKit/ReportIssue.swift +++ b/native/Sources/BrewBrowserKit/ReportIssue.swift @@ -52,24 +52,23 @@ enum ReportIssue { /// Bold heading for the field's InfoButton popover (mirrors Tauri). var infoTitle: String { switch self { - case .summary: return "About this summary" - case .categories: return "About categories" - case .tags: return "About tags" - case .useCases: return "About use cases" - case .similar: return "About similar packages" + case .summary: return L10n.string("About this summary") + case .categories: return L10n.string("About categories") + case .tags: return L10n.string("About tags") + case .useCases: return L10n.string("About use cases") + case .similar: return L10n.string("About similar packages") } } /// Provenance copy for the InfoButton popover — verbatim from the Tauri /// `InfoButton` bodies, with the field-specific tail. var infoBody: String { - let base = "Generated offline at build time by Claude Haiku 4.5 — no network or LLM calls happen while you use brew-browser. Open an issue if " switch self { - case .summary: return base + "the summary looks off and we'll fix it in the next release." - case .categories: return base + "a category looks off and we'll fix it in the next release." - case .tags: return base + "a tag looks off and we'll fix it in the next release." - case .useCases: return base + "these use cases look off and we'll fix them in the next release." - case .similar: return base + "these suggestions look off and we'll fix them in the next release." + case .summary: return L10n.string("detail.aboutSummary.body") + case .categories: return L10n.string("detail.aboutCategories.body") + case .tags: return L10n.string("detail.aboutTags.body") + case .useCases: return L10n.string("detail.aboutUseCases.body") + case .similar: return L10n.string("detail.aboutSimilar.body") } } } diff --git a/native/Sources/BrewBrowserKit/Resources/Localizable.xcstrings b/native/Sources/BrewBrowserKit/Resources/Localizable.xcstrings new file mode 100644 index 0000000..21ea4ba --- /dev/null +++ b/native/Sources/BrewBrowserKit/Resources/Localizable.xcstrings @@ -0,0 +1,13286 @@ +{ + "sourceLanguage": "en", + "strings": { + "AI & ML": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "AI & ML" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "ИИ и ML" + } + } + } + }, + "Browsers": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browsers" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Браузеры" + } + } + } + }, + "Cloud & DevOps": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cloud & DevOps" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Облако и DevOps" + } + } + } + }, + "Communication": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Communication" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Коммуникации" + } + } + } + }, + "Data": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Data" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Данные" + } + } + } + }, + "Developer Tools": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Developer Tools" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Инструменты разработчика" + } + } + } + }, + "Editors & IDEs": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Editors & IDEs" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Редакторы и IDE" + } + } + } + }, + "Education": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Education" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Образование" + } + } + } + }, + "Games & Entertainment": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Games & Entertainment" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Игры и развлечения" + } + } + } + }, + "Graphics & Design": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Graphics & Design" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Графика и дизайн" + } + } + } + }, + "Music": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Music" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Музыка" + } + } + } + }, + "Office & Docs": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Office & Docs" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офис и документы" + } + } + } + }, + "Productivity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Productivity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Продуктивность" + } + } + } + }, + "System Utilities": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System Utilities" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Системные утилиты" + } + } + } + }, + "Video & Audio": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Video & Audio" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Видео и аудио" + } + } + } + }, + "Writing": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Writing" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Работа с текстом" + } + } + } + }, + "Uncategorized": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Uncategorized" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Без категории" + } + } + } + }, + "app.controls": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "App controls" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Элементы управления приложением" + } + } + } + }, + "chrome.github.connectionStatus": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub connection status" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Статус подключения GitHub" + } + } + } + }, + "chrome.donate.aria": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Donate to brew-browser on GitHub Sponsors" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поддержать brew-browser через GitHub Sponsors" + } + } + } + }, + "chrome.donate.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Donate via GitHub Sponsors" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поддержать через GitHub Sponsors" + } + } + } + }, + "command.openActivity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть активность" + } + } + } + }, + "command.openDiscover": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Discover" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть каталог" + } + } + } + }, + "command.openLibrary": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Library" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть библиотеку" + } + } + } + }, + "command.openSnapshots": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Snapshots" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть снимки" + } + } + } + }, + "command.openTrending": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Trending" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть тренды" + } + } + } + }, + "command.refreshLibrary": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh Library" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить библиотеку" + } + } + } + }, + "command.refreshSnapshots": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh Snapshots" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить снимки" + } + } + } + }, + "command.refreshTrending": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh Trending" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить тренды" + } + } + } + }, + "command.toggleActivityDrawer": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Activity drawer" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать или скрыть журнал активности" + } + } + } + }, + "github.connectedAs": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub: connected as @{username}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "GitHub: выполнен вход как @{username}" + } + } + } + }, + "github.scopeIncomplete": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub: signed in, but scope incomplete — click to fix" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "GitHub: вход выполнен, но не хватает разрешений — нажмите, чтобы исправить" + } + } + } + }, + "keyboard.close": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "close" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "закрыть" + } + } + } + }, + "keyboard.navigate": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "navigate" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "перейти" + } + } + } + }, + "keyboard.open": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "open" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "открыть" + } + } + } + }, + "language.english": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "English" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Английский" + } + } + } + }, + "language.hint.reload": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Changing the language reloads brew-browser." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "После смены языка brew-browser перезагрузится." + } + } + } + }, + "language.label": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Language" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Язык" + } + } + } + }, + "language.russian": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Russian" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Русский" + } + } + } + }, + "language.system": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System language" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Язык системы" + } + } + } + }, + "nav.activity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Активность" + } + } + } + }, + "nav.dashboard": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dashboard" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сводка" + } + } + } + }, + "nav.discover": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Discover" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Каталог" + } + } + } + }, + "nav.library": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Library" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Библиотека" + } + } + } + }, + "nav.primary": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Primary navigation" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Основная навигация" + } + } + } + }, + "nav.services": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Службы" + } + } + } + }, + "nav.snapshots": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Snapshots" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снимки" + } + } + } + }, + "nav.trending": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Trending" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тренды" + } + } + } + }, + "nav.showSidebar": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show sidebar" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать боковую панель" + } + } + } + }, + "nav.hideSidebar": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Hide sidebar" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скрыть боковую панель" + } + } + } + }, + "package.installed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "installed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "установлен" + } + } + } + }, + "package.kind.cask": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cask" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "cask-пакет" + } + } + } + }, + "package.kind.formula": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "formula" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "формула" + } + } + } + }, + "palette.empty": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No results." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ничего не найдено." + } + } + } + }, + "palette.group.actions": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Actions" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Действия" + } + } + } + }, + "palette.group.commands": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Commands" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Команды" + } + } + } + }, + "palette.group.index": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Index" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Каталог" + } + } + } + }, + "palette.group.installed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установленные" + } + } + } + }, + "palette.results": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command palette results" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Результаты палитры команд" + } + } + } + }, + "palette.search.placeholder": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Type a command, package, or section." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Введите команду, пакет или раздел." + } + } + } + }, + "palette.searchLabel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command palette search" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск в палитре команд" + } + } + } + }, + "palette.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command palette" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Палитра команд" + } + } + } + }, + "palette.group.view": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "View" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вид" + } + } + } + }, + "search.allResultsInDiscover": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "See all results in Discover →" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Все результаты в Каталоге →" + } + } + } + }, + "search.clear": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить" + } + } + } + }, + "search.clearSearch": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear search" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить поиск" + } + } + } + }, + "search.noMatches": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No matches for \"{query}\"." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "По запросу «{query}» ничего не найдено." + } + } + } + }, + "search.packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск пакетов" + } + } + } + }, + "search.packagesPlaceholder": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search packages…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск пакетов…" + } + } + } + }, + "search.results": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search results" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Результаты поиска" + } + } + } + }, + "search.searching": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Searching…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Идёт поиск…" + } + } + } + }, + "settings.open": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Settings" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть настройки" + } + } + } + }, + "settings.close": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close Settings" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть настройки" + } + } + } + }, + "settings.paneLabel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings — {section}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройки — {section}" + } + } + } + }, + "settings.sections": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings sections" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделы настроек" + } + } + } + }, + "settings.section.about": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О приложении" + } + } + } + }, + "settings.section.activity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Активность" + } + } + } + }, + "settings.section.appearance": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Appearance" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Оформление" + } + } + } + }, + "settings.section.brew": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Brew" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Brew" + } + } + } + }, + "settings.section.github": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + } + } + }, + "settings.section.network": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Network" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сеть" + } + } + } + }, + "settings.shortcutTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings (⌘,)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройки (⌘,)" + } + } + } + }, + "settings.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройки" + } + } + } + }, + "theme.change": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Change theme" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Изменить тему" + } + } + } + }, + "theme.dark": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dark" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тёмная" + } + } + } + }, + "theme.light": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Light" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Светлая" + } + } + } + }, + "theme.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Theme: {theme}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тема: {theme}" + } + } + } + }, + "theme.system": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Системная" + } + } + } + }, + "trending.window.30d": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "30d" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "30 дн." + } + } + } + }, + "trending.window.90d": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "90d" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "90 дн." + } + } + } + }, + "trending.window.365d": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "365d" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "365 дн." + } + } + } + }, + "updates.available": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update available" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Доступно обновление" + } + } + } + }, + "updates.availableLabel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update available: brew-browser {version}. Click to open Settings." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Доступно обновление: brew-browser {version}. Нажмите, чтобы открыть Настройки." + } + } + } + }, + "updates.dismissVersion": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dismiss update notification for v{version}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скрыть уведомление об обновлении до v{version}" + } + } + } + }, + "updates.installing": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installing update" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устанавливаем обновление" + } + } + } + }, + "updates.skipVersion": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Skip this version" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пропустить эту версию" + } + } + } + }, + "updates.versionAvailable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser {version} is available" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Доступна версия brew-browser {version}" + } + } + } + }, + "vulnerability.packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "{count} vulnerable packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уязвимых пакетов: {count}" + } + } + } + }, + "About": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О приложении" + } + } + } + }, + "About Brew Browser": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About Brew Browser" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О Brew Browser" + } + } + } + }, + "Activity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Активность" + } + } + } + }, + "Actions": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Actions" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Действия" + } + } + } + }, + "Advanced": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Advanced" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Дополнительно" + } + } + } + }, + "AI features": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "AI features" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Функции ИИ" + } + } + } + }, + "All": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "All" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Все" + } + } + } + }, + "All Categories": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "All Categories" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Все категории" + } + } + } + }, + "All current": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "All current" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Всё актуально" + } + } + } + }, + "Analytics": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Analytics" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Аналитика" + } + } + } + }, + "Appearance": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Appearance" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Оформление" + } + } + } + }, + "App version": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "App version" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Версия приложения" + } + } + } + }, + "Autoremove": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Autoremove" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить лишние зависимости" + } + } + } + }, + "Autoremove unused dependencies": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Autoremove unused dependencies" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить неиспользуемые зависимости" + } + } + } + }, + "Auto-check daily": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Auto-check daily" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверять ежедневно" + } + } + } + }, + "Body (Markdown)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Body (Markdown)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Текст (Markdown)" + } + } + } + }, + "Brew": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Brew" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Brew" + } + } + } + }, + "Browse": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browse" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обзор" + } + } + } + }, + "Cancel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отмена" + } + } + } + }, + "Casks": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Casks" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Cask-пакеты" + } + } + } + }, + "Casks (Caskroom)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Casks (Caskroom)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Cask-пакеты (Caskroom)" + } + } + } + }, + "Caveats": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Caveats" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Примечания" + } + } + } + }, + "Check for updates now": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check for updates now" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить обновления сейчас" + } + } + } + }, + "Check now": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check now" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить" + } + } + } + }, + "Choose packages to upgrade": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose packages to upgrade" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выберите пакеты для обновления" + } + } + } + }, + "Choose…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выбрать…" + } + } + } + }, + "Categories unavailable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Categories unavailable" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Категории недоступны" + } + } + } + }, + "Categories": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Categories" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Категории" + } + } + } + }, + "Clear": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить" + } + } + } + }, + "Clear completed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear completed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить завершённые" + } + } + } + }, + "Clear Finished": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear Finished" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить завершённые" + } + } + } + }, + "Clear categories": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear categories" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сбросить категории" + } + } + } + }, + "Clear category filter": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear category filter" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сбросить фильтр категорий" + } + } + } + }, + "Clear filter": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clear filter" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сбросить фильтр" + } + } + } + }, + "Click the code to copy it.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Click the code to copy it." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нажмите код, чтобы скопировать его." + } + } + } + }, + "Close": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть" + } + } + } + }, + "Command Palette…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command Palette…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Палитра команд…" + } + } + } + }, + "Composition": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Composition" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Состав" + } + } + } + }, + "Confirm before uninstall / zap": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Confirm before uninstall / zap" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подтверждать удаление и полную очистку (zap)" + } + } + } + }, + "Confirmations": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Confirmations" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подтверждения" + } + } + } + }, + "Contacting GitHub…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Contacting GitHub…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Связываемся с GitHub…" + } + } + } + }, + "Create": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Create" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Создать" + } + } + } + }, + "Dashboard": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dashboard" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сводка" + } + } + } + }, + "Dark": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dark" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тёмная" + } + } + } + }, + "Default landing": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Default landing" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Стартовый раздел" + } + } + } + }, + "Delete": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Delete" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить" + } + } + } + }, + "Dependency": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dependency" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Зависимость" + } + } + } + }, + "Description": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Description" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Описание" + } + } + } + }, + "Download cache": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Download cache" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Кэш загрузок" + } + } + } + }, + "Deselect all": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deselect all" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снять выбор" + } + } + } + }, + "Discover": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Discover" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Каталог" + } + } + } + }, + "Dismiss": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dismiss" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скрыть" + } + } + } + }, + "Dismiss for this session": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dismiss for this session" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скрыть до конца сеанса" + } + } + } + }, + "Donate to the project": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Donate to the project" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поддержать проект" + } + } + } + }, + "Enter this code:": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enter this code:" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Введите этот код:" + } + } + } + }, + "Esc": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Esc" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Esc" + } + } + } + }, + "Export…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Export…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Экспорт…" + } + } + } + }, + "Exposure": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Exposure" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уязвимости" + } + } + } + }, + "Fetch latest categories & descriptions": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Fetch latest categories & descriptions" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновлять категории и описания" + } + } + } + }, + "Fetch trending history": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Fetch trending history" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружать историю трендов" + } + } + } + }, + "File issue": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "File issue" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сообщить о проблеме" + } + } + } + }, + "File an issue": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "File an issue" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сообщить о проблеме" + } + } + } + }, + "Filter": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Filter" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Фильтр" + } + } + } + }, + "Formulae": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Formulae" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Формулы" + } + } + } + }, + "Formulae (Cellar)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Formulae (Cellar)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Формулы (Cellar)" + } + } + } + }, + "GitHub": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "GitHub" + } + } + } + }, + "Greedy upgrades (include self-updating casks)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Greedy upgrades (include self-updating casks)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновлять также самообновляемые cask-пакеты" + } + } + } + }, + "Homebrew": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Homebrew" + } + } + } + }, + "Icon source": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Icon source" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Источник иконки" + } + } + } + }, + "Import…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Import…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Импорт…" + } + } + } + }, + "Install": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установить" + } + } + } + }, + "Install brew-vulns": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install brew-vulns" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установить brew-vulns" + } + } + } + }, + "Install complete. Relaunch to use the new version.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install complete. Relaunch to use the new version." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установка завершена. Перезапустите приложение, чтобы перейти на новую версию." + } + } + } + }, + "Install trend": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install trend" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Динамика установок" + } + } + } + }, + "Installed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установлено" + } + } + } + }, + "Installed only": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installed only" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Только установленные" + } + } + } + }, + "Installing…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installing…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устанавливаем…" + } + } + } + }, + "instead": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "instead" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "вместо" + } + } + } + }, + "Keep last N completed jobs": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Keep last N completed jobs" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Хранить последние N завершённых задач" + } + } + } + }, + "Language": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Language" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Язык" + } + } + } + }, + "Labels": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Labels" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Метки" + } + } + } + }, + "Latest": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Latest" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Актуальная версия" + } + } + } + }, + "Library": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Library" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Библиотека" + } + } + } + }, + "License": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "License" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Лицензия" + } + } + } + }, + "License mismatch": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "License mismatch" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Лицензии не совпадают" + } + } + } + }, + "Light": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Light" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Светлая" + } + } + } + }, + "Lines per job": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Lines per job" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Строк на задачу" + } + } + } + }, + "Loading GitHub stats…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading GitHub stats…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем статистику GitHub…" + } + } + } + }, + "Loading settings…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading settings…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем настройки…" + } + } + } + }, + "Loading sign-in status…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading sign-in status…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверяем вход…" + } + } + } + }, + "Logs (var/log)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Logs (var/log)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Логи (var/log)" + } + } + } + }, + "Manual": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Manual" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вручную" + } + } + } + }, + "Name": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Name" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Имя" + } + } + } + }, + "Network": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Network" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сеть" + } + } + } + }, + "New Snapshot": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "New Snapshot" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новый снимок" + } + } + } + }, + "No beta channel in this release.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No beta channel in this release." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "В этом релизе нет бета-канала." + } + } + } + }, + "No background services": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No background services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Фоновых служб нет" + } + } + } + }, + "No known vulnerabilities.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No known vulnerabilities." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Известных уязвимостей нет." + } + } + } + }, + "No known vulnerabilities": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No known vulnerabilities" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Известных уязвимостей нет" + } + } + } + }, + "No outdated packages.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No outdated packages." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устаревших пакетов нет." + } + } + } + }, + "No results.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No results." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ничего не найдено." + } + } + } + }, + "No snapshots yet": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No snapshots yet" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снимков пока нет" + } + } + } + }, + "Not enough data yet": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not enough data yet" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пока недостаточно данных" + } + } + } + }, + "Not scanned yet.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not scanned yet." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверка ещё не выполнялась." + } + } + } + }, + "Offline Mode": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим" + } + } + } + }, + "Off": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Off" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выкл." + } + } + } + }, + "OK": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "OK" + } + } + } + }, + "Open in browser": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open in browser" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть в браузере" + } + } + } + }, + "Open in Finder": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open in Finder" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать в Finder" + } + } + } + }, + "Outbound paths (read-only)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Outbound paths (read-only)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Исходящие сетевые обращения (только чтение)" + } + } + } + }, + "Package detail": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Package detail" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Карточка пакета" + } + } + } + }, + "Patched in": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Patched in" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Исправлено в" + } + } + } + }, + "Pinned": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pinned" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закреплён" + } + } + } + }, + "Pinned packages are not upgraded by brew. Unpin from the package detail page.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pinned packages are not upgraded by brew. Unpin from the package detail page." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закреплённые пакеты не обновляются через brew. Открепите пакет в его карточке." + } + } + } + }, + "Recent": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Recent" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Недавние" + } + } + } + }, + "RECENT": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "RECENT" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "НЕДАВНИЕ" + } + } + } + }, + "Refresh": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить" + } + } + } + }, + "Refresh from brew.sh →": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh from brew.sh →" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить с brew.sh →" + } + } + } + }, + "Refreshing…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refreshing…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновляем…" + } + } + } + }, + "Remove": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Убрать" + } + } + } + }, + "Repository": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Repository" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Репозиторий" + } + } + } + }, + "Reset to defaults": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reset to defaults" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сбросить настройки" + } + } + } + }, + "Resize package detail panel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Resize package detail panel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Изменить ширину карточки пакета" + } + } + } + }, + "Restore": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restore" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Восстановить" + } + } + } + }, + "Retry": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Retry" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Повторить" + } + } + } + }, + "Re-scan all": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Re-scan all" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить всё заново" + } + } + } + }, + "Scan all packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scan all packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить все пакеты" + } + } + } + }, + "Scan installed packages for known vulnerabilities": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scan installed packages for known vulnerabilities" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверять установленные пакеты на известные уязвимости" + } + } + } + }, + "Scan now": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scan now" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить сейчас" + } + } + } + }, + "Scanning all…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scanning all…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверяем все пакеты…" + } + } + } + }, + "Scanning…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scanning…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверяем…" + } + } + } + }, + "Scrub — also remove the latest versions' cached downloads (more aggressive)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scrub — also remove the latest versions' cached downloads (more aggressive)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тщательная очистка: удалить также кэш загрузок последних версий (агрессивнее)" + } + } + } + }, + "Search": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск" + } + } + } + }, + "Search packages…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search packages…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск пакетов…" + } + } + } + }, + "Searching…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Searching…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Идёт поиск…" + } + } + } + }, + "Security": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Security" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Безопасность" + } + } + } + }, + "Select all": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Select all" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выбрать все" + } + } + } + }, + "Send Homebrew install analytics": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Send Homebrew install analytics" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отправлять аналитику установок Homebrew" + } + } + } + }, + "Service": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Service" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Служба" + } + } + } + }, + "Services": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Службы" + } + } + } + }, + "Settings": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройки" + } + } + } + }, + "Settings file is unreadable.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings file is unreadable." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Файл настроек не читается." + } + } + } + }, + "Show GitHub stats on package pages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show GitHub stats on package pages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывать статистику GitHub на страницах пакетов" + } + } + } + }, + "Show sidebar": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show sidebar" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать боковую панель" + } + } + } + }, + "Hide sidebar": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Hide sidebar" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скрыть боковую панель" + } + } + } + }, + "Sign in": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вход" + } + } + } + }, + "Sign in to GitHub": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in to GitHub" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Войти в GitHub" + } + } + } + }, + "Sign in with GitHub": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in with GitHub" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Войти через GitHub" + } + } + } + }, + "Sign out": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign out" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выйти" + } + } + } + }, + "Sign-in denied.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign-in denied." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вход отклонён." + } + } + } + }, + "Similar packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Similar packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Похожие пакеты" + } + } + } + }, + "Size": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Size" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Размер" + } + } + } + }, + "Snapshot": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Snapshot" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снимок" + } + } + } + }, + "Snapshots": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Snapshots" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снимки" + } + } + } + }, + "Stable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stable" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Стабильный" + } + } + } + }, + "Start": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Start" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запустить" + } + } + } + }, + "Stop": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Остановить" + } + } + } + }, + "Storage": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Storage" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Хранилище" + } + } + } + }, + "Status": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Status" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Статус" + } + } + } + }, + "Submit": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Submit" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отправить" + } + } + } + }, + "Support brew-browser via GitHub Sponsors": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Support brew-browser via GitHub Sponsors" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поддержать brew-browser через GitHub Sponsors" + } + } + } + }, + "System": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Системная" + } + } + } + }, + "System language": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "System language" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Язык системы" + } + } + } + }, + "Tags": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tags" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Теги" + } + } + } + }, + "Tap": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Tap" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Tap" + } + } + } + }, + "Terminal": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Terminal" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Терминал" + } + } + } + }, + "Theme": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Theme" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тема" + } + } + } + }, + "Title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Title" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Заголовок" + } + } + } + }, + "Token": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Token" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Токен" + } + } + } + }, + "Top categories in your library": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Top categories in your library" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Главные категории в библиотеке" + } + } + } + }, + "Trending": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Trending" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тренды" + } + } + } + }, + "Trending cache TTL": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Trending cache TTL" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "TTL кэша трендов" + } + } + } + }, + "Try again": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Try again" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Попробовать снова" + } + } + } + }, + "Type": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Type" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тип" + } + } + } + }, + "Type a command, package, or section.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Type a command, package, or section." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Введите команду, пакет или раздел." + } + } + } + }, + "Update available": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update available" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Доступно обновление" + } + } + } + }, + "Update channel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update channel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Канал обновлений" + } + } + } + }, + "Updates": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Updates" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновления" + } + } + } + }, + "Updates available": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Updates available" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Доступны обновления" + } + } + } + }, + "Vulnerable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vulnerable" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уязвимые" + } + } + } + }, + "Velocity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Velocity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Динамика" + } + } + } + }, + "Upgrade": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgrade" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить" + } + } + } + }, + "Upgrade all": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgrade all" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить всё" + } + } + } + }, + "Upgrade available": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgrade available" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Доступно обновление" + } + } + } + }, + "Upgrade to fix": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgrade to fix" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить, чтобы исправить" + } + } + } + }, + "Version": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Version" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Версия" + } + } + } + }, + "Verbose — list every file removed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Verbose — list every file removed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подробный вывод: перечислить все удаляемые файлы" + } + } + } + }, + "View vulnerable packages →": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "View vulnerable packages →" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать уязвимые пакеты →" + } + } + } + }, + "Waiting for authorization…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Waiting for authorization…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ожидаем авторизации…" + } + } + } + }, + "Watch": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Watch" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отслеживать" + } + } + } + }, + "Watching": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Watching" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отслеживается" + } + } + } + }, + "Why install this?": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Why install this?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Зачем это устанавливать?" + } + } + } + }, + "Window": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Window" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Окно" + } + } + } + }, + "Window vibrancy": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Window vibrancy" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Эффект прозрачности окна" + } + } + } + }, + "— all enrichment is baked into the app binary.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "— all enrichment is baked into the app binary." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "— всё уже включено в приложение." + } + } + } + }, + "Zero LLM calls are made from your machine": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Zero LLM calls are made from your machine" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "С вашего компьютера не выполняются LLM-запросы" + } + } + } + }, + "Zero telemetry. Zero accounts.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Zero telemetry. Zero accounts." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Никакой телеметрии. Никаких учётных записей." + } + } + } + }, + "built with": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "built with" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "создано с помощью" + } + } + } + }, + "close": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "close" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "закрыть" + } + } + } + }, + "close Esc": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "close Esc" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть (Esc)" + } + } + } + }, + "dependency": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "dependency" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "зависимость" + } + } + } + }, + "formula": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "formula" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "формула" + } + } + } + }, + "cask": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "cask" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "cask" + } + } + } + }, + "installed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "installed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "установлено" + } + } + } + }, + "not present": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "not present" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "нет на диске" + } + } + } + }, + "pinned": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "pinned" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "закреплён" + } + } + } + }, + "source": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "source" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "источник" + } + } + } + }, + "unavailable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "unavailable" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "недоступно" + } + } + } + }, + "working…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "working…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "выполняется…" + } + } + } + }, + "Adopt existing": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Adopt existing" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Использовать существующую копию" + } + } + } + }, + "Backend not available": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Backend not available" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Бэкенд недоступен" + } + } + } + }, + "Catalog refresh failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Catalog refresh failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось обновить каталог" + } + } + } + }, + "Change theme": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Change theme" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Изменить тему" + } + } + } + }, + "Changing the language reloads brew-browser.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Changing the language reloads brew-browser." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "После смены языка brew-browser перезагрузится." + } + } + } + }, + "Cleanup failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cleanup failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось очистить кэш" + } + } + } + }, + "Clean up cache…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clean up cache…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить кэш…" + } + } + } + }, + "Clean up": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Clean up" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очистить" + } + } + } + }, + "Cleaning…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cleaning…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очищаем…" + } + } + } + }, + "Cleaning up Homebrew cache": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cleaning up Homebrew cache" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Очищаем кэш Homebrew" + } + } + } + }, + "Command palette": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command palette" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Палитра команд" + } + } + } + }, + "Command palette search": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command palette search" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск в палитре команд" + } + } + } + }, + "Command palette results": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command palette results" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Результаты палитры команд" + } + } + } + }, + "Couldn't change brew analytics": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't change brew analytics" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось изменить аналитику brew" + } + } + } + }, + "Couldn't load services": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't load services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить службы" + } + } + } + }, + "Couldn't load snapshots": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't load snapshots" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить снимки" + } + } + } + }, + "Couldn't load packages.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't load packages." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить пакеты." + } + } + } + }, + "Couldn't open in file manager": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't open in file manager" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось открыть в файловом менеджере" + } + } + } + }, + "Couldn't reveal in Finder": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't reveal in Finder" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось показать в Finder" + } + } + } + }, + "Disk probe failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disk probe failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось проверить диск" + } + } + } + }, + "Donate via GitHub Sponsors": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Donate via GitHub Sponsors" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поддержать через GitHub Sponsors" + } + } + } + }, + "Donate to brew-browser on GitHub Sponsors": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Donate to brew-browser on GitHub Sponsors" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поддержать brew-browser через GitHub Sponsors" + } + } + } + }, + "Everything is up to date": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Everything is up to date" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Всё актуально" + } + } + } + }, + "English": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "English" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Английский" + } + } + } + }, + "Follows the macOS theme when set to System.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Follows the macOS theme when set to System." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Следует теме macOS, если выбрана системная тема." + } + } + } + }, + "Failed to load packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Failed to load packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить пакеты" + } + } + } + }, + "Force remove": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Force remove" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить принудительно" + } + } + } + }, + "GitHub connection status": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub connection status" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Статус подключения GitHub" + } + } + } + }, + "Homebrew analytics disabled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew analytics disabled" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Аналитика Homebrew отключена" + } + } + } + }, + "Homebrew analytics enabled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew analytics enabled" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Аналитика Homebrew включена" + } + } + } + }, + "Homebrew was not found on this Mac.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew was not found on this Mac." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Homebrew не найден на этом Mac." + } + } + } + }, + "Index": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Index" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Индекс" + } + } + } + }, + "Installed as a dependency, not requested directly": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installed as a dependency, not requested directly" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установлено как зависимость, а не вручную" + } + } + } + }, + "Invalid URL": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Invalid URL" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Недопустимый URL" + } + } + } + }, + "Loading your packages…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading your packages…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем пакеты…" + } + } + } + }, + "Measuring disk usage…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Measuring disk usage…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Считаем место на диске…" + } + } + } + }, + "Nothing's run yet.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing's run yet." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пока ничего не запускалось." + } + } + } + }, + "Open Activity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть активность" + } + } + } + }, + "Open Discover": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Discover" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть каталог" + } + } + } + }, + "Open Library": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Library" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть библиотеку" + } + } + } + }, + "Open Settings": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Settings" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть настройки" + } + } + } + }, + "Open Snapshots": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Snapshots" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть снимки" + } + } + } + }, + "Open Trending": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Trending" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть тренды" + } + } + } + }, + "Open Library, filter outdated": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Library, filter outdated" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть библиотеку с фильтром устаревших" + } + } + } + }, + "Other": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Other" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Другое" + } + } + } + }, + "Pick which packages to upgrade": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pick which packages to upgrade" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выбрать пакеты для обновления" + } + } + } + }, + "Refresh Library": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh Library" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить библиотеку" + } + } + } + }, + "Refresh Snapshots": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh Snapshots" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить снимки" + } + } + } + }, + "Refresh Trending": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh Trending" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить тренды" + } + } + } + }, + "Refresh disk usage": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh disk usage" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить сведения о диске" + } + } + } + }, + "Refreshed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refreshed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновлено" + } + } + } + }, + "Report an issue on GitHub": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Report an issue on GitHub" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сообщить о проблеме на GitHub" + } + } + } + }, + "Report to brew-browser": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Report to brew-browser" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отправить отчёт в brew-browser" + } + } + } + }, + "Remove from history": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove from history" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Убрать из истории" + } + } + } + }, + "Remove it anyway, ignoring packages that depend on it.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove it anyway, ignoring packages that depend on it." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить принудительно, игнорируя пакеты, которые от него зависят." + } + } + } + }, + "Remove unused dependencies?": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove unused dependencies?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить неиспользуемые зависимости?" + } + } + } + }, + "Removing unused dependencies": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Removing unused dependencies" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удаляем неиспользуемые зависимости" + } + } + } + }, + "Removing…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Removing…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удаляем…" + } + } + } + }, + "Replace the existing copy with a fresh Homebrew install.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Replace the existing copy with a fresh Homebrew install." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Заменить существующую копию новой установкой из Homebrew." + } + } + } + }, + "Run brew doctor": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Run brew doctor" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запустить brew doctor" + } + } + } + }, + "Run brew doctor and stream the diagnostics into Activity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Run brew doctor and stream the diagnostics into Activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запустить brew doctor и вывести диагностику в панель активности" + } + } + } + }, + "Run brew update, refresh the catalog from formulae.brew.sh, and reload your installed list": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Run brew update, refresh the catalog from formulae.brew.sh, and reload your installed list" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запустить brew update, обновить каталог с formulae.brew.sh и перечитать список установленных пакетов" + } + } + } + }, + "Russian": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Russian" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Русский" + } + } + } + }, + "Search packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск пакетов" + } + } + } + }, + "Search results": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search results" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Результаты поиска" + } + } + } + }, + "See the Activity drawer.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "See the Activity drawer." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подробности в панели Активность." + } + } + } + }, + "Settings (⌘,)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings (⌘,)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройки (⌘,)" + } + } + } + }, + "Settings sections": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings sections" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разделы настроек" + } + } + } + }, + "Submit issue": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Submit issue" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отправить проблему" + } + } + } + }, + "This will install packages from the snapshot. Existing packages are skipped.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will install packages from the snapshot. Existing packages are skipped." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Будут установлены пакеты из снимка. Уже установленные пакеты пропускаются." + } + } + } + }, + "Toggle Activity drawer": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Toggle Activity drawer" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать или скрыть журнал активности" + } + } + } + }, + "Upgrade-all failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgrade-all failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось обновить всё" + } + } + } + }, + "Upgrading…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgrading…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновляем…" + } + } + } + }, + "When off, only Homebrew's native metadata appears. Categories (sidebar tile grid, donut chart, chip filters) are also hidden because they're AI-generated.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "When off, only Homebrew's native metadata appears. Categories (sidebar tile grid, donut chart, chip filters) are also hidden because they're AI-generated." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Если выключено, отображаются только штатные метаданные Homebrew. Категории (плитки в боковой панели, кольцевая диаграмма и фильтры по категориям) тоже скрываются, потому что они сгенерированы ИИ." + } + } + } + }, + "When on, brew-browser shows extra metadata generated at build time by AI: friendly names, expanded descriptions, use cases, similar package suggestions, and category tags.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "When on, brew-browser shows extra metadata generated at build time by AI: friendly names, expanded descriptions, use cases, similar package suggestions, and category tags." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Если включено, brew-browser показывает дополнительные метаданные, сгенерированные ИИ при сборке: понятные названия, расширенные описания, сценарии использования, похожие пакеты и теги категорий." + } + } + } + }, + "Which section opens when you launch brew-browser.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Which section opens when you launch brew-browser." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Какой раздел открывается при запуске brew-browser." + } + } + } + }, + "Requires app restart to take effect. The default (HudWindow) matches the rest of macOS.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Requires app restart to take effect. The default (HudWindow) matches the rest of macOS." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вступит в силу после перезапуска приложения. Значение по умолчанию (HudWindow) соответствует остальной macOS." + } + } + } + }, + "all packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "all packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "все пакеты" + } + } + } + }, + "brew commands kicked off from here will show up in this list.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew commands kicked off from here will show up in this list." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Команды brew, запущенные из приложения, появятся в этом списке." + } + } + } + }, + "disabled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "disabled" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "отключено" + } + } + } + }, + "Advisory": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Advisory" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Бюллетень безопасности" + } + } + } + }, + "Catalog unavailable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Catalog unavailable" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Каталог недоступен" + } + } + } + }, + "Check this package for known vulnerabilities.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check this package for known vulnerabilities." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить этот пакет на известные уязвимости." + } + } + } + }, + "Commands": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Commands" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Команды" + } + } + } + }, + "Couldn't load packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't load packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить пакеты" + } + } + } + }, + "Couldn't load trending": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't load trending" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить тренды" + } + } + } + }, + "Copy": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Copy" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скопировать" + } + } + } + }, + "Copy the command to the clipboard": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Copy the command to the clipboard" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скопировать команду в буфер обмена" + } + } + } + }, + "Deprecated": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deprecated" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устарело" + } + } + } + }, + "Disabled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disabled" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отключено" + } + } + } + }, + "Disabled by Offline Mode": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disabled by Offline Mode" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отключено офлайн-режимом" + } + } + } + }, + "Blocked by Offline Mode. Disable in Settings → Network.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Blocked by Offline Mode. Disable in Settings → Network." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Заблокировано офлайн-режимом. Отключите его в разделе «Настройки → Сеть»." + } + } + } + }, + "Fetching install counts from formulae.brew.sh…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Fetching install counts from formulae.brew.sh…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем счётчики установок с formulae.brew.sh…" + } + } + } + }, + "Homebrew isn't installed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew isn't installed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Homebrew не установлен" + } + } + } + }, + "Install Homebrew": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install Homebrew" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установить Homebrew" + } + } + } + }, + "Install `brew vulns` to scan this package for known vulnerabilities.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install `brew vulns` to scan this package for known vulnerabilities." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установите `brew vulns`, чтобы проверить этот пакет на известные уязвимости." + } + } + } + }, + "Install the Xcode Command Line Tools": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install the Xcode Command Line Tools" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установить Xcode Command Line Tools" + } + } + } + }, + "Installs": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installs" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установки" + } + } + } + }, + "Installs, upgrades, uninstalls, and updates show up here.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installs, upgrades, uninstalls, and updates show up here." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Здесь отображаются установки, обновления пакетов, удаления и обновления Homebrew." + } + } + } + }, + "Loading snapshots…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading snapshots…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем снимки…" + } + } + } + }, + "Loading the Homebrew catalog…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading the Homebrew catalog…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем каталог Homebrew…" + } + } + } + }, + "Members also categorized here": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Members also categorized here" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пакеты, которые также входят в эту категорию" + } + } + } + }, + "Members of this category with no other category": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Members of this category with no other category" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пакеты только из этой категории" + } + } + } + }, + "No activity yet": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No activity yet" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Активности пока нет" + } + } + } + }, + "No packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пакетов нет" + } + } + } + }, + "No results": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No results" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ничего не найдено" + } + } + } + }, + "No summary available — view details": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No summary available — view details" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нет краткого описания — открыть подробности" + } + } + } + }, + "Not installed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not installed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не установлено" + } + } + } + }, + "Offline Mode is on — trending needs formulae.brew.sh.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode is on — trending needs formulae.brew.sh." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим включён — трендам нужен доступ к formulae.brew.sh." + } + } + } + }, + "Open Dashboard": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Dashboard" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть сводку" + } + } + } + }, + "Open Services": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть службы" + } + } + } + }, + "Open Terminal": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open Terminal" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть Терминал" + } + } + } + }, + "Outdated": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Outdated" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Есть обновления" + } + } + } + }, + "Paste the official install command (from brew.sh) into Terminal:": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Paste the official install command (from brew.sh) into Terminal:" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вставьте официальную команду установки с brew.sh в Терминал:" + } + } + } + }, + "Reading your Homebrew install…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reading your Homebrew install…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Читаем конфигурацию Homebrew…" + } + } + } + }, + "Restart": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезапустить" + } + } + } + }, + "Runs brew vulns across your whole install — it can't scan a single package": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Runs brew vulns across your whole install — it can't scan a single package" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запускает brew vulns для всей установки Homebrew; отдельный пакет проверить нельзя" + } + } + } + }, + "Save your current setup so you can restore it on another Mac. Snapshots live in ~/Library/Application Support/brew-browser/brewfiles/ — findable outside the app too.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Save your current setup so you can restore it on another Mac. Snapshots live in ~/Library/Application Support/brew-browser/brewfiles/ — findable outside the app too." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сохраните текущую конфигурацию, чтобы восстановить её на другом Mac. Снимки лежат в ~/Library/Application Support/brew-browser/brewfiles/ — их можно найти и вне приложения." + } + } + } + }, + "Scanning all packages…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scanning all packages…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверяем все пакеты…" + } + } + } + }, + "Sign in to GitHub happens on first action.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in to GitHub happens on first action." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вход в GitHub откроется при первом действии." + } + } + } + }, + "Sign in to GitHub to star this repository": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in to GitHub to star this repository" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Войдите в GitHub, чтобы поставить звезду репозиторию" + } + } + } + }, + "Sign in to GitHub to watch this repository": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in to GitHub to watch this repository" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Войдите в GitHub, чтобы отслеживать репозиторий" + } + } + } + }, + "Snapshot error": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Snapshot error" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ошибка снимка" + } + } + } + }, + "Star": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Star" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поставить звезду" + } + } + } + }, + "Star this repository": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Star this repository" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поставить звезду репозиторию" + } + } + } + }, + "Starred": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Starred" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Звезда поставлена" + } + } + } + }, + "Stop watching": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop watching" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не отслеживать" + } + } + } + }, + "Stored in ~/Library/Application Support/brew-browser/brewfiles/": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stored in ~/Library/Application Support/brew-browser/brewfiles/" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Хранится в ~/Library/Application Support/brew-browser/brewfiles/" + } + } + } + }, + "The Brewfile will be removed from disk. This cannot be undone.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The Brewfile will be removed from disk. This cannot be undone." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Brewfile будет удалён с диска. Это действие нельзя отменить." + } + } + } + }, + "The Homebrew installer needs them. Run this in Terminal and follow the prompt:": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The Homebrew installer needs them. Run this in Terminal and follow the prompt:" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Они нужны установщику Homebrew. Выполните это в Терминале и следуйте подсказкам:" + } + } + } + }, + "The bundled package catalog couldn't be loaded.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The bundled package catalog couldn't be loaded." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить встроенный каталог пакетов." + } + } + } + }, + "The brew-vulns subcommand isn't installed.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The brew-vulns subcommand isn't installed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подкоманда brew-vulns не установлена." + } + } + } + }, + "settings.vulnerabilities.cleanResult": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No known vulnerabilities across your installed packages." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "В установленных пакетах известные уязвимости не найдены." + } + } + } + }, + "settings.vulnerabilities.helperInstalledTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-vulns installed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-vulns установлен" + } + } + } + }, + "settings.vulnerabilities.initialScanBody": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Running an initial scan now…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запускаем первичную проверку…" + } + } + } + }, + "settings.vulnerabilities.installFailedTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't install brew-vulns" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось установить brew-vulns" + } + } + } + }, + "settings.vulnerabilities.installPrompt": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vulnerability scanning needs the official brew vulns subcommand. Install it now? This runs brew install homebrew/brew-vulns/brew-vulns." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Для проверки уязвимостей нужна официальная подкоманда brew vulns. Установить её сейчас? Будет выполнено brew install homebrew/brew-vulns/brew-vulns." + } + } + } + }, + "settings.vulnerabilities.lastScan": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Last scan" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Последняя проверка" + } + } + } + }, + "settings.vulnerabilities.offlineHint": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode is on — vulnerability scanning is suppressed even if this toggle is on. Disable Offline Mode above to enable scanning." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим включён — проверка уязвимостей заблокирована, даже если переключатель включён. Отключите офлайн-режим выше, чтобы разрешить проверку." + } + } + } + }, + "settings.vulnerabilities.privacyHint": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Opt-in, off by default. When on, brew-browser shells out to the official brew vulns subcommand (Homebrew project), which queries OSV.dev (operated by Google) for known vulnerabilities affecting your installed formulae. If you're also signed in to GitHub, individual GHSA-IDs are enriched with details from api.github.com. Findings are cached locally; no package list leaves your machine except the queries brew vulns itself makes to OSV." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Опционально, выключено по умолчанию. При включении brew-browser запускает официальную подкоманду brew vulns проекта Homebrew: она запрашивает OSV.dev (сервис Google) об известных уязвимостях в установленных формулах. Если вы также вошли в GitHub, отдельные GHSA-ID дополняются сведениями из api.github.com. Результаты кэшируются локально; список пакетов не покидает ваш компьютер, кроме запросов, которые сама brew vulns отправляет в OSV." + } + } + } + }, + "settings.vulnerabilities.scanAllTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Run brew vulns against every installed formula" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запустить brew vulns для всех установленных формул" + } + } + } + }, + "This installs packages from the snapshot. Existing packages are skipped.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This installs packages from the snapshot. Existing packages are skipped." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Будут установлены пакеты из снимка. Уже установленные пакеты пропускаются." + } + } + } + }, + "Use": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Используйте" + } + } + } + }, + "User": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "User" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пользователь" + } + } + } + }, + "Uninstall": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Uninstall" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить" + } + } + } + }, + "Unstar this repository": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Unstar this repository" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Убрать звезду с репозитория" + } + } + } + }, + "Vulnerability Scanning": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vulnerability Scanning" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверка уязвимостей" + } + } + } + }, + "Waiting for Homebrew… the app continues automatically once it's installed.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Waiting for Homebrew… the app continues automatically once it's installed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ожидаем Homebrew… приложение продолжит работу автоматически после установки." + } + } + } + }, + "Watch for activity": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Watch for activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отслеживать активность" + } + } + } + }, + "Working… (see Activity)": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Working… (see Activity)" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выполняется… см. «Активность»" + } + } + } + }, + "Your browser opened to GitHub's device page. Enter this code (it's copied to your clipboard):": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Your browser opened to GitHub's device page. Enter this code (it's copied to your clipboard):" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Браузер открыл страницу GitHub для ввода кода устройства. Введите этот код (он уже скопирован в буфер обмена):" + } + } + } + }, + "brew-browser hit an unexpected error running this command.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser hit an unexpected error running this command." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-browser столкнулся с неожиданной ошибкой при запуске этой команды." + } + } + } + }, + "brew-browser is a window onto Homebrew — install it and the app takes it from there.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser is a window onto Homebrew — install it and the app takes it from there." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-browser работает поверх Homebrew: установите Homebrew, а дальше приложение всё подхватит." + } + } + } + }, + "formulae.brew.sh returned no entries.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "formulae.brew.sh returned no entries." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "formulae.brew.sh не вернул записей." + } + } + } + }, + "instead.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "instead." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "вместо него." + } + } + } + }, + "Already running": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Already running" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уже запущено" + } + } + } + }, + "App Store Version Detected": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "App Store Version Detected" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обнаружена версия из App Store" + } + } + } + }, + "Cancel sign in": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel sign in" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отменить вход" + } + } + } + }, + "Click to copy": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Click to copy" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нажмите, чтобы скопировать" + } + } + } + }, + "Code copied to clipboard": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Code copied to clipboard" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Код скопирован в буфер обмена" + } + } + } + }, + "Code expired. Please try again.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Code expired. Please try again." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Срок действия кода истёк. Попробуйте ещё раз." + } + } + } + }, + "Couldn't copy code": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't copy code" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось скопировать код" + } + } + } + }, + "Delete failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Delete failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось удалить" + } + } + } + }, + "Deleted snapshot": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deleted snapshot" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снимок удалён" + } + } + } + }, + "Export failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Export failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось экспортировать" + } + } + } + }, + "Exported snapshot": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Exported snapshot" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снимок экспортирован" + } + } + } + }, + "Import failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Import failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось импортировать" + } + } + } + }, + "Imported snapshot": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Imported snapshot" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снимок импортирован" + } + } + } + }, + "Install & Override": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install & Override" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установить с заменой" + } + } + } + }, + "Installation Blocked": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installation Blocked" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установка заблокирована" + } + } + } + }, + "Loading brew services…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading brew services…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем службы brew…" + } + } + } + }, + "No background services.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No background services." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Фоновых служб нет." + } + } + } + }, + "Install something like postgresql, redis, or nginx and they'll show up here.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install something like postgresql, redis, or nginx and they'll show up here." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установите, например, postgresql, redis или nginx — службы появятся здесь." + } + } + } + }, + "Refresh services": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить службы" + } + } + } + }, + "Restore complete": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restore complete" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Восстановление завершено" + } + } + } + }, + "Restore failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restore failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось восстановить" + } + } + } + }, + "Snapshot failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Snapshot failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось сохранить снимок" + } + } + } + }, + "Snapshot saved": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Snapshot saved" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снимок сохранён" + } + } + } + }, + "Start service": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Start service" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запустить службу" + } + } + } + }, + "Stop service": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop service" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Остановить службу" + } + } + } + }, + "Restart service": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart service" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезапустить службу" + } + } + } + }, + "Not running": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not running" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не запущено" + } + } + } + }, + "Brew services": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Brew services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Службы brew" + } + } + } + }, + "Open package details": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open package details" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть карточку пакета" + } + } + } + }, + "Save your current setup so you can restore it on another machine. Snapshots live in ~/.local/share/brew-browser/brewfiles/ — findable outside the app too.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Save your current setup so you can restore it on another machine. Snapshots live in ~/.local/share/brew-browser/brewfiles/ — findable outside the app too." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сохраните текущую конфигурацию, чтобы восстановить её на другой машине. Снимки лежат в ~/.local/share/brew-browser/brewfiles/ — их можно найти и вне приложения." + } + } + } + }, + "This will remove the package from your system.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This will remove the package from your system." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пакет будет удалён из системы." + } + } + } + }, + "Overwrite manual installation?": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Overwrite manual installation?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Заменить ручную установку?" + } + } + } + }, + "An existing version was found in your Applications folder. If you proceed, Homebrew will force-install and overwrite the existing bundle.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "An existing version was found in your Applications folder. If you proceed, Homebrew will force-install and overwrite the existing bundle." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "В папке /Applications уже есть эта версия. Если продолжить, Homebrew принудительно установит пакет и заменит существующее приложение." + } + } + } + }, + "An existing App Store version was found in your Applications folder.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "An existing App Store version was found in your Applications folder." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "В папке /Applications уже есть версия, установленная через App Store." + } + } + } + }, + "App Store bundles are system-protected and cannot be overwritten by Homebrew.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "App Store bundles are system-protected and cannot be overwritten by Homebrew." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Приложения из App Store защищены системой, и Homebrew не сможет заменить их напрямую." + } + } + } + }, + "Resolution: delete the App Store version from /Applications, then install it again via Homebrew.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Resolution: delete the App Store version from /Applications, then install it again via Homebrew." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Решение: удалите из /Applications версию, установленную через App Store, затем установите приложение через Homebrew." + } + } + } + }, + "Use the Sign in with GitHub button in Settings.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use the Sign in with GitHub button in Settings." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Используйте кнопку «Войти через GitHub» в настройках." + } + } + } + }, + "Re-authorize": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Re-authorize" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить разрешения" + } + } + } + }, + "Upgrade failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgrade failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось обновить" + } + } + } + }, + "Upgraded packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgraded packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пакеты обновлены" + } + } + } + }, + "Watching package": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Watching package" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отслеживаем пакет" + } + } + } + }, + "Stopped watching package": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stopped watching package" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отслеживание пакета отключено" + } + } + } + }, + "Starred package": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Starred package" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Звезда поставлена" + } + } + } + }, + "Unstarred package": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Unstarred package" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Звезда убрана" + } + } + } + }, + "Signed in as GitHub user.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Signed in as GitHub user." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выполнен вход в GitHub." + } + } + } + }, + "settings.activity.rangeDefault": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Range {min}-{max}. Default {default}." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Диапазон {min}–{max}. По умолчанию {default}." + } + } + } + }, + "settings.activity.retentionNote": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "These limits apply to future job persistence. Existing retained data is not trimmed retroactively." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новые ограничения применяются только к задачам, которые будут сохранены после изменения настройки. Уже сохранённая история не сокращается задним числом." + } + } + } + }, + "settings.brew.analyticsDesc.beforeCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew sends anonymous install analytics to formulae.brew.sh by default. This toggle flips Homebrew's setting (the same as running " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Homebrew по умолчанию отправляет анонимную аналитику установок на formulae.brew.sh. Этот переключатель меняет настройку Homebrew (то же, что выполнить " + } + } + } + }, + "settings.brew.analyticsDesc.afterCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " in your terminal)." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " в Терминале)." + } + } + } + }, + "settings.brew.analyticsToggle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Send Homebrew install analytics" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отправлять аналитику установок Homebrew" + } + } + } + }, + "settings.brew.confirmationsDesc": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Destructive actions (Uninstall, Zap, Delete Brewfile) ask before proceeding. Power users can turn this off once they're sure." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перед необратимыми действиями (удаление пакета, zap-очистка, удаление Brewfile) приложение запрашивает подтверждение. Опытные пользователи могут отключить это, когда будут уверены." + } + } + } + }, + "settings.brew.advancedDesc.beforeCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Extra " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Дополнительные параметры " + } + } + } + }, + "settings.brew.advancedDesc.afterCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " options for power users." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " для опытных пользователей." + } + } + } + }, + "settings.brew.greedyDesc.beforeFlag": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Adds " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Добавляет " + } + } + } + }, + "settings.brew.greedyDesc.between": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " to " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " к " + } + } + } + }, + "settings.brew.greedyDesc.afterCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " so casks that update themselves (like Chrome) are upgraded too. Off by default - greedy can churn apps that manage their own updates." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": ", чтобы обновлялись и cask-пакеты с собственным механизмом обновления (например, Chrome). По умолчанию выключено: режим --greedy может лишний раз переустанавливать такие приложения." + } + } + } + }, + "settings.brew.autoremoveDesc.beforeCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Runs " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запускает " + } + } + } + }, + "settings.brew.autoremoveDesc.afterCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " to uninstall formulae that were installed only as dependencies and are no longer needed by anything." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": ", чтобы удалить формулы, установленные только как зависимости и больше не нужные ни одному пакету." + } + } + } + }, + "settings.brew.autoremoveConfirm.beforeCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This runs " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Будет выполнено " + } + } + } + }, + "settings.brew.autoremoveConfirm.afterCommand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": ", which uninstalls formulae that were installed only as dependencies and are no longer required by anything else." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": ": команда удалит формулы, установленные только как зависимости и больше не используемые другими пакетами." + } + } + } + }, + "settings.liveEnrichment.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Live category & description updates" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Свежие категории и описания" + } + } + } + }, + "settings.liveEnrichment.hint.beforeHost": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser ships with a built-in set of AI categories and descriptions. When on, it refreshes them from " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-browser поставляется со встроенными ИИ-категориями и описаниями. Если включено, приложение обновляет их с " + } + } + } + }, + "settings.liveEnrichment.hint.afterHost": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": ": a tiny version check on refresh, the full category list when it's newer, and a per-package description when you open that package's detail. Only the package name you're viewing is sent (one HTTP GET); no IP is logged at the server, no cookies. Same first-party host as Enhanced Trending - a distinct trust boundary from the always-on Homebrew paths. Requires AI Features on." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": ": при обновлении проверяет версию, загружает полный список категорий, если он новее, и получает описание пакета, когда вы открываете его карточку. Отправляется только имя просматриваемого пакета (один HTTP GET); сервер не логирует IP и не использует файлы cookie. Это собственный эндпоинт проекта brew-browser, отдельная зона доверия от постоянно доступных путей Homebrew. Требует включить функции ИИ." + } + } + } + }, + "settings.liveEnrichment.offline": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode is on - this toggle is locked off. Turn Offline Mode off above to enable live updates." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим включён — переключатель заблокирован. Отключите офлайн-режим выше, чтобы разрешить обновление категорий и описаний." + } + } + } + }, + "settings.trendingHistory.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Enhanced Trending History" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Расширенная история трендов" + } + } + } + }, + "settings.trendingHistory.hint.beforeHost": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "When on, brew-browser fetches per-package historical install trends from " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Если включено, brew-browser загружает историческую динамику установок по пакетам с " + } + } + } + }, + "settings.trendingHistory.hint.afterHost": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " to power per-row sparklines on the Trending tab and a chart on each package's detail panel. Only the package name you're viewing is sent (one HTTP GET per package); no IP is logged at the server, no cookies, no fingerprinting. The endpoint is operated by the brew-browser project - a distinct trust boundary from the always-on Homebrew analytics paths above." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " для мини-графиков в строках вкладки «Тренды» и графика в карточке пакета. Отправляется только имя просматриваемого пакета (один HTTP GET на пакет); сервер не логирует IP, не использует файлы cookie и не собирает цифровой отпечаток. Этот эндпоинт поддерживает проект brew-browser — это отдельная зона доверия от постоянно доступных выше путей аналитики Homebrew." + } + } + } + }, + "settings.trendingHistory.offline": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode is on - this toggle is locked off. Turn Offline Mode off above to enable history fetching." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим включён — переключатель заблокирован. Отключите офлайн-режим выше, чтобы разрешить загрузку истории." + } + } + } + }, + "settings.about.zeroTelemetryBody": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser does not collect telemetry. It does not phone home. It does not have user accounts. Every outbound network request is documented in Settings -> Network and the README, and only fires when you take an action that requires it." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-browser не собирает телеметрию, не обращается к своим серверам без необходимости и не использует учётные записи. Все исходящие сетевые запросы описаны в «Настройки → Сеть» и README и выполняются только для действий, которым они нужны." + } + } + } + }, + "about.closeAria": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close About" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть окно «О приложении»" + } + } + } + }, + "about.tagline": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "A native macOS GUI for Homebrew." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нативный интерфейс macOS для Homebrew." + } + } + } + }, + "about.creditsBody.beforeClaude": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " - the multi-agent toolkit that orchestrated the waves (Backend Architect, Frontend Developer, Security Engineer, Code Reviewer, Technical Writer, and friends), powered by " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " — многоагентный набор инструментов, который координировал волны работы (Backend Architect, Frontend Developer, Security Engineer, Code Reviewer, Technical Writer и другие), при участии " + } + } + } + }, + "about.creditsBody.afterClaude": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " in the terminal, running " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " в терминале, модель " + } + } + } + }, + "about.thanksLinux": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Thanks also to the Homebrew project for the package data, every formula maintainer for their work, and Tauri for the native shell." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Благодарим Homebrew за каталог пакетов, мейнтейнеров формул — за их работу, а Tauri — за нативную оболочку." + } + } + } + }, + "about.thanksMac": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Thanks also to the Homebrew project for the package data, every formula and cask maintainer for their work, and Tauri for the native shell." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Благодарим Homebrew за каталог пакетов, мейнтейнеров формул и cask-пакетов — за их работу, а Tauri — за нативную оболочку." + } + } + } + }, + "about.posture": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Zero telemetry. Zero accounts. Every outbound network call is documented in Settings -> Network." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Никакой телеметрии. Никаких учётных записей. Все исходящие сетевые обращения описаны в «Настройки → Сеть»." + } + } + } + }, + "Repo": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Repo" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Репозиторий" + } + } + } + }, + "Built with": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Built with" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Создано с помощью" + } + } + } + }, + "Open GitHub Sponsors in your browser": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open GitHub Sponsors in your browser" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть GitHub Sponsors в браузере" + } + } + } + }, + "Close detail panel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close detail panel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть карточку пакета" + } + } + } + }, + "Loading package detail…": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading package detail…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем карточку пакета…" + } + } + } + }, + "Couldn't load detail": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't load detail" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить карточку" + } + } + } + }, + "Installed by User": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installed by User" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Найдено вне Homebrew" + } + } + } + }, + "On request": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "On request" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вручную" + } + } + } + }, + "Total on-disk size of the installed keg (all versions).": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Total on-disk size of the installed keg (all versions)." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Общий размер установленного keg на диске (все версии)." + } + } + } + }, + "About categories": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About categories" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О категориях" + } + } + } + }, + "About this package's categories": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About this package's categories" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О категориях этого пакета" + } + } + } + }, + "About tags": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About tags" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О тегах" + } + } + } + }, + "About these tags": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About these tags" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Об этих тегах" + } + } + } + }, + "About this summary": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About this summary" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Об описании" + } + } + } + }, + "detail.aboutCategories.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if a category looks off and we'll fix it in the next release." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если категория выглядит неверно, и мы исправим её в следующем релизе." + } + } + } + }, + "detail.aboutTags.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if a tag looks off and we'll fix it in the next release." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если тег выглядит неверно, и мы исправим его в следующем релизе." + } + } + } + }, + "detail.aboutSummary.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if the summary looks off and we'll fix it in the next release." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если описание выглядит неверно, и мы исправим его в следующем релизе." + } + } + } + }, + "No longer available via Homebrew.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No longer available via Homebrew." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Больше недоступно через Homebrew." + } + } + } + }, + "May be removed in a future Homebrew update.": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "May be removed in a future Homebrew update." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Может быть удалено при одном из будущих обновлений Homebrew." + } + } + } + }, + "Run brew upgrade for this package": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Run brew upgrade for this package" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запустить brew upgrade для этого пакета" + } + } + } + }, + "Open advisory": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open advisory" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть бюллетень безопасности" + } + } + } + }, + "Patched version": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Patched version" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Исправленная версия" + } + } + } + }, + "detail.trend.bootstrap": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Bootstrap + daily snapshots - granularity grows over time" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Начальные и ежедневные снимки — детализация растёт со временем" + } + } + } + }, + "detail.trend.daily": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Daily install snapshots" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ежедневные снимки установок" + } + } + } + }, + "Use cases": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use cases" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сценарии использования" + } + } + } + }, + "About use cases": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About use cases" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О сценариях использования" + } + } + } + }, + "About these use cases": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About these use cases" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Об этих сценариях использования" + } + } + } + }, + "detail.aboutUseCases.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if these use cases look off and we'll fix them in the next release." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если сценарии использования выглядят неверно, и мы исправим их в следующем релизе." + } + } + } + }, + "About similar packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About similar packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О похожих пакетах" + } + } + } + }, + "About these similar packages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About these similar packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Об этих похожих пакетах" + } + } + } + }, + "detail.aboutSimilar.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if these suggestions look off and we'll fix them in the next release." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если рекомендации выглядят неверно, и мы исправим их в следующем релизе." + } + } + } + }, + "GitHub repository statistics": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub repository statistics" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Статистика репозитория GitHub" + } + } + } + }, + "Couldn't load GitHub stats": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't load GitHub stats" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить статистику GitHub" + } + } + } + }, + "Dependencies": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dependencies" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Зависимости" + } + } + } + }, + "Required by": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Required by" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Зависимые пакеты" + } + } + } + }, + "About reverse dependencies": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About reverse dependencies" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Об обратных зависимостях" + } + } + } + }, + "detail.aboutReverseDependencies.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Packages in the bundled catalog that list this one as a dependency. Computed offline by inverting the catalog graph - no network or brew calls. Build-only and opt-in (recommended/optional) edges are labelled; everything else is a required runtime dependency." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пакеты во встроенном каталоге, где этот пакет указан как зависимость. Вычисляется офлайн инверсией графа каталога — без сетевых запросов и вызовов brew. Зависимости только для сборки и опциональные/рекомендуемые связи помечены; остальное — обязательные runtime-зависимости." + } + } + } + }, + "Conflicts with": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Conflicts with" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Конфликтует с" + } + } + } + }, + "detail.action.reinstall": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reinstall" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переустановить" + } + } + } + }, + "detail.category.openTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Browse all packages in {category}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать все пакеты в категории «{category}»" + } + } + } + }, + "detail.caskUnavailableLinux": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Casks require macOS — this package can't be installed on Linux." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Cask-пакеты требуют macOS — этот пакет нельзя установить в Linux." + } + } + } + }, + "detail.deprecation.disabled.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This package is disabled and is no longer available — it will be removed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Этот пакет отключён и больше недоступен — он будет удалён." + } + } + } + }, + "detail.deprecation.deprecated.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This package is deprecated and scheduled for removal. It still installs for now." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Этот пакет устарел и запланирован к удалению. Пока его ещё можно установить." + } + } + } + }, + "detail.deprecation.disabledDate.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disabled: %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отключён: %@" + } + } + } + }, + "detail.deprecation.deprecatedDate.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deprecated: %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устарел: %@" + } + } + } + }, + "detail.deprecation.disabled.tooltip": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disabled — this package is no longer available and will be removed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отключено — пакет больше недоступен и будет удалён." + } + } + } + }, + "detail.deprecation.deprecated.tooltip": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deprecated — still installable, but scheduled for removal." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устарело — пока устанавливается, но запланировано к удалению." + } + } + } + }, + "detail.deprecation.useInstead": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Use instead:" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Используйте вместо него:" + } + } + } + }, + "about.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About Brew Browser" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О Brew Browser" + } + } + } + }, + "action.cancel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cancel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отмена" + } + } + } + }, + "action.close": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Close" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закрыть" + } + } + } + }, + "action.deselectAll": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deselect all" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Снять выбор" + } + } + } + }, + "action.installing": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installing…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устанавливаем…" + } + } + } + }, + "action.refresh": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить" + } + } + } + }, + "action.retry": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Retry" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Повторить" + } + } + } + }, + "action.selectAll": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Select all" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выбрать все" + } + } + } + }, + "activity.collapse": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Collapse activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Свернуть активность" + } + } + } + }, + "activity.copyOutput": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Copy output" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скопировать вывод" + } + } + } + }, + "activity.drawer.hide": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Hide Activity drawer" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скрыть журнал активности" + } + } + } + }, + "activity.drawer.show": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show Activity drawer" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать журнал активности" + } + } + } + }, + "activity.expand": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Expand activity" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Развернуть активность" + } + } + } + }, + "activity.keepJobs.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Keep last %d completed jobs" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Хранить последние %d завершённых задач" + } + } + } + }, + "activity.limits.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "These limits apply to future saved jobs only. Existing history is not shortened retroactively." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Новые ограничения применяются только к задачам, которые будут сохранены после изменения настройки. Уже сохранённая история не сокращается задним числом." + } + } + } + }, + "activity.linesPerJob.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Keep last %d log lines per job" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Хранить последние %d строк лога на задачу" + } + } + } + }, + "activity.notifyOnCompletion": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Notify when jobs complete" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уведомлять о завершении задач" + } + } + } + }, + "activity.notifyOnCompletion.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Shows a macOS notification when a long-running brew job finishes." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывает уведомление macOS, когда долгая задача brew завершается." + } + } + } + }, + "chrome.donate.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Support brew-browser via GitHub Sponsors" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поддержать brew-browser через GitHub Sponsors" + } + } + } + }, + "chrome.donate.menu": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sponsor brew-browser…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поддержать brew-browser…" + } + } + } + }, + "icon.source.faviconFrom": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Favicon from {homepage}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Favicon с {homepage}" + } + } + } + }, + "icon.source.homepage": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "homepage" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "домашняя страница" + } + } + } + }, + "icon.source.installedApp": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "installed app" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "установленное приложение" + } + } + } + }, + "icon.source.none": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "none" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "нет" + } + } + } + }, + "enrichment.fetchLatest": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Fetch latest categories & descriptions" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновлять категории и описания" + } + } + } + }, + "enrichment.fetchLatest.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser ships with built-in AI categories and descriptions. When enabled, it checks for newer first-party enrichment data and fetches a package description when you open the package detail. Only the viewed package name is sent." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-browser поставляется со встроенными ИИ-категориями и описаниями. Если включено, приложение проверяет наличие более свежих данных проекта и получает описание пакета, когда вы открываете его карточку. Отправляется только имя просматриваемого пакета." + } + } + } + }, + "filter.all": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "All" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Все" + } + } + } + }, + "filter.dependency": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dependencies" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Зависимости" + } + } + } + }, + "filter.manual": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Manual" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вручную" + } + } + } + }, + "filter.outdated": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Outdated" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Есть обновления" + } + } + } + }, + "filter.vulnerable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vulnerable" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уязвимые" + } + } + } + }, + "github.connectedAs.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub: connected as @%@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "GitHub: выполнен вход как @%@" + } + } + } + }, + "github.deviceFlowInstructions.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open %@ and enter this code. It has been copied to your clipboard." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Откройте %@ и введите этот код. Он уже скопирован в буфер обмена." + } + } + } + }, + "github.keychain.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The token is stored in macOS Keychain. Sign-in is optional and uses the minimum scopes needed for stars, watches, and issues." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Токен хранится в macOS Keychain. Вход необязателен и использует минимальные разрешения для звёзд, отслеживания и создания issue в GitHub." + } + } + } + }, + "github.noInstalledHomepages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "None of your installed packages have a GitHub homepage." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "У установленных пакетов нет домашней страницы GitHub." + } + } + } + }, + "github.signIn": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вход" + } + } + } + }, + "github.signIn.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in with GitHub to star, watch, and file package metadata issues from brew-browser." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Войдите в GitHub, чтобы ставить звёзды, отслеживать репозитории и создавать issue в GitHub по метаданным пакетов из brew-browser." + } + } + } + }, + "github.signInWithGitHub": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign in with GitHub" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Войти через GitHub" + } + } + } + }, + "github.signOut": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign out" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выйти" + } + } + } + }, + "github.signedInAs": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Signed in as" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выполнен вход как" + } + } + } + }, + "github.signedInAsToast": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Signed in as @{username}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выполнен вход как @{username}" + } + } + } + }, + "github.signedInToast": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Signed in to GitHub" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выполнен вход в GitHub" + } + } + } + }, + "github.signInDenied": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "GitHub sign-in denied" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вход в GitHub отклонён" + } + } + } + }, + "github.signInExpired": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign-in code expired" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Срок действия кода истёк" + } + } + } + }, + "github.signInExpiredBody": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Try again." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Попробуйте ещё раз." + } + } + } + }, + "github.stargazers": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stargazers" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Звёзды" + } + } + } + }, + "github.forks": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Forks" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Форки" + } + } + } + }, + "github.licenseMismatchTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew reports: {brew} · GitHub reports: {github}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew сообщает: {brew} · GitHub сообщает: {github}" + } + } + } + }, + "github.scopeRequired.title.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "%@ needs more access" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нужны дополнительные разрешения: %@" + } + } + } + }, + "github.scopeRequired.body.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Needs the \"%@\" GitHub permission. Click to grant it without signing out." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не хватает разрешения GitHub «%@». Нажмите, чтобы выдать его без выхода из аккаунта." + } + } + } + }, + "github.yourCode": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Your code" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ваш код" + } + } + } + }, + "brew.status.checking": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Checking Homebrew…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверяем Homebrew…" + } + } + } + }, + "brew.status.installed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew is installed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Homebrew установлен." + } + } + } + }, + "brew.status.notFound": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew not found" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew не найден" + } + } + } + }, + "brew.status.notFoundOnPath": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew not found on PATH." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Homebrew не найден в PATH." + } + } + } + }, + "brew.status.ready.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew %@ · prefix %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Homebrew %@ · prefix %@" + } + } + } + }, + "brew.status.unknown": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew status unknown" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Статус Homebrew неизвестен" + } + } + } + }, + "brew.status.unknownWithError": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew status unknown - {error}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Статус Homebrew неизвестен — {error}" + } + } + } + }, + "catalog.refresh.noWritableDir": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't locate a writable catalog directory." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось найти доступный для записи каталог." + } + } + } + }, + "catalog.refresh.failed.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Catalog refresh failed: %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось обновить каталог: %@" + } + } + } + }, + "catalog.refresh.malformedResponse": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew.sh returned an unexpected catalog format." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew.sh вернул каталог в неожиданном формате." + } + } + } + }, + "catalog.refresh.emptyResponse.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew.sh returned an empty catalog (formulae=%d, casks=%d)." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew.sh вернул пустой каталог (формулы=%d, cask-пакеты=%d)." + } + } + } + }, + "catalog.refresh.compression": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't compress the refreshed catalog." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось сжать обновлённый каталог." + } + } + } + }, + "catalog.refresh.offlineBlocked": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline mode is on — catalog refresh is blocked. Disable it in Settings → Network." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим включён — обновление каталога заблокировано. Отключите его в разделе «Настройки → Сеть»." + } + } + } + }, + "catalog.refresh.brewFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew failed during refresh (exit {code})." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew завершился с ошибкой при обновлении каталога (код {code})." + } + } + } + }, + "menu.go": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Go" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переход" + } + } + } + }, + "package.kind.casks": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Casks" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Cask-пакеты" + } + } + } + }, + "package.kind.formulae": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Formulae" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Формулы" + } + } + } + }, + "package.pinned": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pinned" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закреплён" + } + } + } + }, + "palette.title.menu": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Command Palette…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Палитра команд…" + } + } + } + }, + "security.brewVulnsInstalled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-vulns is installed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-vulns установлен" + } + } + } + }, + "security.brewVulnsMissing.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vulnerability scanning needs the official brew vulns subcommand." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Для проверки уязвимостей нужна официальная подкоманда brew vulns." + } + } + } + }, + "security.installBrewVulns": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install brew-vulns" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установить brew-vulns" + } + } + } + }, + "security.offlineSuppressed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode is on — vulnerability scanning is suppressed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим включён — проверка уязвимостей заблокирована." + } + } + } + }, + "security.scanInstalled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scan installed packages for known vulnerabilities" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверять установленные пакеты на известные уязвимости" + } + } + } + }, + "security.scanInstalled.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Opt-in, off by default. Runs the official brew vulns subcommand for installed formulae and stores findings locally." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Опционально, выключено по умолчанию. Запускает официальную подкоманду brew vulns для установленных формул и хранит результаты локально." + } + } + } + }, + "security.viewVulnerablePackages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "View vulnerable packages →" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать уязвимые пакеты →" + } + } + } + }, + "detail.security.cleanStale": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No advisories as of the last scan — re-scan to confirm" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "На момент последней проверки известных уязвимостей нет — проверьте ещё раз для подтверждения" + } + } + } + }, + "detail.security.lastScan.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Last scan: %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Последняя проверка: %@" + } + } + } + }, + "detail.security.notScanned.detail": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not scanned yet. Scanning checks every installed package — brew vulns can't scan just one." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверка ещё не выполнялась. Проверка проходит по всем установленным пакетам: brew vulns не умеет проверять только один пакет." + } + } + } + }, + "detail.security.openAdvisory.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open advisory: %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть бюллетень безопасности: %@" + } + } + } + }, + "detail.security.patchedIn.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Patched in %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Исправлено в %@" + } + } + } + }, + "detail.security.rescanHelp": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Runs brew vulns across your whole install — it can't scan a single package" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запускает brew vulns по всей установленной библиотеке; отдельный пакет проверить нельзя" + } + } + } + }, + "onboarding.aria": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew setup" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настройка Homebrew" + } + } + } + }, + "onboarding.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Let's get Homebrew set up" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Настроим Homebrew" + } + } + } + }, + "onboarding.body.mac": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser is a friendly face for Homebrew, the package manager for your Mac. Install it once and your library appears here automatically — no relaunch needed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-browser — удобный интерфейс для Homebrew, пакетного менеджера для Mac. Установите Homebrew один раз, и библиотека появится здесь автоматически — перезапуск не нужен." + } + } + } + }, + "onboarding.body.linux": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser is a friendly face for Homebrew, which runs on Linux too. Install it once and your library appears here automatically — no relaunch needed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-browser — удобный интерфейс для Homebrew, который работает и в Linux. Установите Homebrew один раз, и библиотека появится здесь автоматически — перезапуск не нужен." + } + } + } + }, + "onboarding.clt.before": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install the Xcode Command Line Tools first: run" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сначала установите Xcode Command Line Tools: выполните" + } + } + } + }, + "onboarding.clt.after": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "in Terminal and follow the prompts." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "в Терминале и следуйте подсказкам." + } + } + } + }, + "onboarding.linuxDeps.before": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install Homebrew's build dependencies first — on Debian/Ubuntu:" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сначала установите зависимости сборки Homebrew. Для Debian/Ubuntu:" + } + } + } + }, + "onboarding.linuxDeps.after": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "other distros: see the \"Homebrew on Linux\" page at docs.brew.sh." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "для других дистрибутивов см. страницу \"Homebrew on Linux\" на docs.brew.sh." + } + } + } + }, + "onboarding.installCopied": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install command copied to clipboard" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Команда установки скопирована" + } + } + } + }, + "onboarding.copyFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't copy command" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось скопировать команду" + } + } + } + }, + "onboarding.openTerminalFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't open Terminal" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось открыть Терминал" + } + } + } + }, + "onboarding.commandCopiedFallback": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "The install command was copied instead — paste it into any terminal." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Команда установки скопирована — вставьте её в любой терминал." + } + } + } + }, + "onboarding.openTerminalOrCopyFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't open Terminal or copy the command" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось открыть Терминал или скопировать команду" + } + } + } + }, + "services.alreadyRunning": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Already running" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уже запущено" + } + } + } + }, + "services.empty.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install something like postgresql, redis, or nginx and services will show up here." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Установите, например, postgresql, redis или nginx — службы появятся здесь." + } + } + } + }, + "services.empty.title": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No background services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Фоновых служб нет" + } + } + } + }, + "services.loadFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't load services" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить службы" + } + } + } + }, + "services.loading": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading brew services…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем службы brew…" + } + } + } + }, + "services.notRunning": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Not running" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не запущено" + } + } + } + }, + "services.restart": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Restart service" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перезапустить службу" + } + } + } + }, + "services.start": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Start service" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запустить службу" + } + } + } + }, + "services.status.error": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "error" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "ошибка" + } + } + } + }, + "services.status.notLoaded": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "not loaded" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "не загружена" + } + } + } + }, + "services.status.running": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "running" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "запущена" + } + } + } + }, + "services.status.scheduled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "scheduled" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "запланирована" + } + } + } + }, + "services.status.stopped": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "stopped" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "остановлена" + } + } + } + }, + "services.status.unknown": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "unknown" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "неизвестно" + } + } + } + }, + "services.stop": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stop service" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Остановить службу" + } + } + } + }, + "settings.appearance.aiFeatures": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "AI features" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Функции ИИ" + } + } + } + }, + "settings.appearance.aiFeatures.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "When off, only Homebrew's native metadata appears. AI-generated categories and descriptions are hidden." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Если выключено, отображаются только штатные метаданные Homebrew. ИИ-категории и описания скрываются." + } + } + } + }, + "settings.appearance.defaultLanding": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Default landing" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Стартовый раздел" + } + } + } + }, + "settings.brew.advanced": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Advanced" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Дополнительно" + } + } + } + }, + "settings.brew.analytics": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Analytics" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Аналитика" + } + } + } + }, + "settings.brew.analytics.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew sends anonymous install analytics to formulae.brew.sh by default. This changes Homebrew's own analytics preference." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Homebrew по умолчанию отправляет анонимную аналитику установок на formulae.brew.sh. Этот переключатель меняет собственную настройку аналитики Homebrew." + } + } + } + }, + "settings.brew.autoremove": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Autoremove" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить лишние зависимости" + } + } + } + }, + "settings.brew.autoremove.confirmMessage": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "This runs brew autoremove to delete formulae installed only as dependencies and no longer needed by any package." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Будет выполнено brew autoremove: команда удалит формулы, установленные только как зависимости и больше не нужные ни одному пакету." + } + } + } + }, + "settings.brew.autoremove.confirmTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove unused dependencies?" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить неиспользуемые зависимости?" + } + } + } + }, + "settings.brew.autoremove.failedToRun": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew autoremove failed to run" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось запустить brew autoremove" + } + } + } + }, + "settings.brew.autoremove.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Runs brew autoremove to remove formulae that were installed only as dependencies and are no longer needed." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Запускает brew autoremove, чтобы удалить формулы, установленные только как зависимости и больше не нужные ни одному пакету." + } + } + } + }, + "settings.brew.autoremoveUnused": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Autoremove unused dependencies" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Удалить неиспользуемые зависимости" + } + } + } + }, + "settings.brew.confirmDestructive": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Confirm before uninstall / zap" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подтверждать удаление и zap-очистку" + } + } + } + }, + "settings.brew.confirmDestructive.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Ask for confirmation before irreversible actions: uninstalling a package, zap cleanup, or deleting a Brewfile." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Перед необратимыми действиями (удаление пакета, zap-очистка, удаление Brewfile) приложение запрашивает подтверждение." + } + } + } + }, + "settings.brew.confirmations": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Confirmations" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Подтверждения" + } + } + } + }, + "settings.brew.greedyUpgrades": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Greedy upgrades" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Расширенное обновление" + } + } + } + }, + "settings.brew.greedyUpgrades.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Include self-updating casks such as Chrome when running brew upgrade --greedy. Off by default." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Включает cask-пакеты с собственным механизмом обновления, например Chrome, при запуске brew upgrade --greedy. По умолчанию выключено." + } + } + } + }, + "settings.brew.sendAnalytics": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Send Homebrew install analytics" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отправлять аналитику установок Homebrew" + } + } + } + }, + "settings.fileUnreadable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings file is unreadable." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Файл настроек не читается." + } + } + } + }, + "settings.github.showStats": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show GitHub stats on package pages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывать статистику GitHub на страницах пакетов" + } + } + } + }, + "settings.github.showStats.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "When signed in, brew-browser checks whether you have starred packages with GitHub homepages." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "После входа brew-browser проверяет, какие пакеты с домашними страницами GitHub вы отметили звёздочкой." + } + } + } + }, + "settings.network.caskIconFetching": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cask icon fetching" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загрузка иконок cask-пакетов" + } + } + } + }, + "settings.network.catalogAutoRefresh": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Catalog auto-refresh" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Автообновление каталога" + } + } + } + }, + "settings.network.catalogStaleThreshold.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show stale catalog banner after %d days" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывать предупреждение об устаревшем каталоге через %d дней" + } + } + } + }, + "settings.network.offlineMode": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим" + } + } + } + }, + "settings.network.offlineMode.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Blocks all brew-browser network calls. brew commands you start still run normally." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Блокирует все сетевые запросы brew-browser. Команды brew, которые вы запускаете сами, продолжают работать как обычно." + } + } + } + }, + "settings.network.offlineMode.on": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode is on" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим включён" + } + } + } + }, + "settings.network.trendingTtl.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Trending cache TTL: %d minutes" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "TTL кэша трендов: %d мин." + } + } + } + }, + "settings.offlineToggleLocked": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode is on — this toggle is locked." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим включён — переключатель заблокирован." + } + } + } + }, + "settings.option.daily": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Daily" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Ежедневно" + } + } + } + }, + "settings.option.installedOnly": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Installed only" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Только установленные" + } + } + } + }, + "settings.option.off": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Off" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выкл." + } + } + } + }, + "settings.option.weekly": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Weekly" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Еженедельно" + } + } + } + }, + "settings.resetToDefaults": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reset to defaults" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сбросить настройки" + } + } + } + }, + "settings.section.security": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Security" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Безопасность" + } + } + } + }, + "settings.section.updates": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Updates" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновления" + } + } + } + }, + "table.actions": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Actions" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Действия" + } + } + } + }, + "table.name": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Name" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Имя" + } + } + } + }, + "table.sortBy": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sort by {label}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сортировать по столбцу «{label}»" + } + } + } + }, + "table.sortedAsc": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sorted by {label}, ascending. Click to reverse." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сортировка по столбцу «{label}» по возрастанию. Нажмите, чтобы изменить порядок." + } + } + } + }, + "table.sortedDesc": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sorted by {label}, descending. Click to reverse." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Сортировка по столбцу «{label}» по убыванию. Нажмите, чтобы изменить порядок." + } + } + } + }, + "table.status": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Status" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Статус" + } + } + } + }, + "table.user": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "User" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пользователь" + } + } + } + }, + "dashboard.refresh.brewUpdateFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew update finished with errors" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew update завершился с ошибками" + } + } + } + }, + "dashboard.refresh.catalogFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Catalog refresh failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось обновить каталог" + } + } + } + }, + "dashboard.refresh.failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось обновить" + } + } + } + }, + "dashboard.refresh.successTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refreshed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновлено" + } + } + } + }, + "dashboard.refresh.successBody": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew taps + catalog + installed list all current" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "tap-репозитории brew, каталог и список установленных пакетов актуальны" + } + } + } + }, + "dashboard.doctor.failedToRun": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew doctor failed to run" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось запустить brew doctor" + } + } + } + }, + "dashboard.upgradeAll.success": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Upgraded {count} packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновлено пакетов: {count}" + } + } + } + }, + "storage.refreshDiskUsage": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Refresh disk usage" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обновить сведения о месте на диске" + } + } + } + }, + "storage.diskProbeFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disk probe failed: {error}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось проверить диск: {error}" + } + } + } + }, + "storage.measuringDiskUsage": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Measuring disk usage… (du -sk on {prefix})" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Считаем место на диске… (du -sk для {prefix})" + } + } + } + }, + "storage.notPresent": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "not present" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "нет на диске" + } + } + } + }, + "storage.revealInFinder": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reveal {path} in Finder" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать {path} в Finder" + } + } + } + }, + "storage.showInFileManager": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show {path} in file manager" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать {path} в файловом менеджере" + } + } + } + }, + "storage.revealLabelInFinder": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Reveal {label} in Finder" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать «{label}» в Finder" + } + } + } + }, + "storage.showLabelInFileManager": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show {label} in file manager" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показать «{label}» в файловом менеджере" + } + } + } + }, + "storage.pathMissing": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Path doesn't exist" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Путь не существует" + } + } + } + }, + "resize.panel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Resize panel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Изменить размер панели" + } + } + } + }, + "resize.dragHint": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Drag to resize · double-click to reset" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Потяните, чтобы изменить размер; двойной щелчок сбрасывает ширину" + } + } + } + }, + "theme.cycle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Cycle theme" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Переключить тему" + } + } + } + }, + "theme.label": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Theme" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Тема" + } + } + } + }, + "trending.fetchHistory": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Fetch trending history" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружать историю трендов" + } + } + } + }, + "trending.fetchHistory.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Downloads per-package trend history for sparklines and package detail charts." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружает историю установок по пакетам для мини-графиков и графиков в карточке пакета." + } + } + } + }, + "trending.sparklineAria": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Install trend sparkline" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "График динамики установок" + } + } + } + }, + "trending.error.backend": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Backend not available: {error}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Бэкенд недоступен: {error}" + } + } + } + }, + "trending.error.failed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Failed to load trending: {error}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось загрузить тренды: {error}" + } + } + } + }, + "trending.error.network": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Couldn't reach formulae.brew.sh" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось подключиться к formulae.brew.sh" + } + } + } + }, + "updates.autoCheck": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Auto-check daily" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверять ежедневно" + } + } + } + }, + "updates.autoCheck.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Automatically check for brew-browser updates once a day." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Автоматически проверять обновления brew-browser раз в день." + } + } + } + }, + "updates.available.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Click to open the updater." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Нажмите, чтобы открыть обновление." + } + } + } + }, + "updates.channel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Channel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Канал" + } + } + } + }, + "updates.channel.label": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Update channel" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Канал обновлений" + } + } + } + }, + "updates.channel.stable": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Stable" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Стабильный" + } + } + } + }, + "updates.check.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Checks the Sparkle update feed for a newer brew-browser build." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверяет ленту Sparkle на наличие новой сборки brew-browser." + } + } + } + }, + "updates.checkForUpdates": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check for Updates…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить обновления…" + } + } + } + }, + "updates.checkNow": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Check now" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Проверить сейчас" + } + } + } + }, + "updates.lastChecked.format": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Last checked: %@" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Последняя проверка: %@" + } + } + } + }, + "updates.never": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Never" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Никогда" + } + } + } + }, + "updates.noBeta": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No beta channel in this release." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "В этом релизе нет бета-канала." + } + } + } + }, + "updates.offlineBlocked": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Offline Mode blocks update checks." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Офлайн-режим блокирует проверку обновлений." + } + } + } + }, + "upgrade.choosePackages": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Choose packages to upgrade" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выберите пакеты для обновления" + } + } + } + }, + "upgrade.noOutdated": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No outdated packages." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устаревших пакетов нет." + } + } + } + }, + "upgrade.pinned.help": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Pinned packages are not upgraded by brew. Unpin from the package detail page." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Закреплённые пакеты не обновляются через brew. Снимите закрепление в карточке пакета." + } + } + } + }, + "deprecated": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "deprecated" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "устарело" + } + } + } + }, + "discover.activeFilters": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Active category filters" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Активные фильтры категорий" + } + } + } + }, + "discover.catalogFetched": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Fetched from formulae.brew.sh" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружено с formulae.brew.sh" + } + } + } + }, + "discover.catalogRefreshed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Catalog refreshed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Каталог обновлён" + } + } + } + }, + "discover.categoryBrowsingHidden": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Category browsing is hidden because AI features are turned off in Settings → Appearance." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Обзор по категориям скрыт, потому что функции ИИ выключены в «Настройки → Оформление»." + } + } + } + }, + "discover.categoryTileAria": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "{category} - {count} packages" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "{category} — {count} пакетов" + } + } + } + }, + "discover.deprecatedDefault": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deprecated - may be removed in a future Homebrew update." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устарело — может быть удалено при одном из будущих обновлений Homebrew." + } + } + } + }, + "discover.deprecatedReason": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Deprecated: {reason}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Устарело: {reason}" + } + } + } + }, + "discover.disabledDefault": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disabled - no longer available via Homebrew." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отключено — больше недоступно через Homebrew." + } + } + } + }, + "discover.disabledReason": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Disabled: {reason}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Отключено: {reason}" + } + } + } + }, + "discover.emptyFilter": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No packages match this filter." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Под этот фильтр пакеты не подходят." + } + } + } + }, + "discover.groupPackagesLabel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Packages in {group}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пакеты в группе «{group}»" + } + } + } + }, + "discover.loadingCategories": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Loading categories…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Загружаем категории…" + } + } + } + }, + "discover.noResults": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Nothing matches \"{query}\"." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "По запросу «{query}» ничего не найдено." + } + } + } + }, + "discover.noResults.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Try a shorter or different term." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Попробуйте более короткий или другой запрос." + } + } + } + }, + "discover.noResultsInCategories": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "No \"{query}\" results in the selected categories." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "По запросу «{query}» нет результатов в выбранных категориях." + } + } + } + }, + "discover.noResultsInCategories.body": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Try removing a chip or broadening the search term." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Уберите фильтр категории или расширьте запрос." + } + } + } + }, + "discover.packagesInCategory": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Packages in {category}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Пакеты в категории «{category}»" + } + } + } + }, + "discover.removeFilter": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Remove {category} filter" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Убрать фильтр «{category}»" + } + } + } + }, + "discover.searchAria": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search Homebrew" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск по Homebrew" + } + } + } + }, + "discover.searchFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск не удался" + } + } + } + }, + "discover.searchPlaceholder": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Search the Homebrew index…" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Поиск в индексе Homebrew…" + } + } + } + }, + "discover.searchPromptTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Type a query to search the Homebrew index" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Введите запрос для поиска в индексе Homebrew" + } + } + } + }, + "discover.staleDismissAria": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Dismiss catalog staleness banner" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Скрыть предупреждение об устаревшем каталоге" + } + } + } + }, + "discover.subgroupNote": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "grouped by overlapping category" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "сгруппировано по пересекающимся категориям" + } + } + } + }, + "discover.subgroupsBody": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Homebrew has no official sub-category data, so this is a co-occurrence grouping: within \"{category}\", packages are bucketed by the other categories they also belong to. A package with several overlapping categories appears in each bucket. \"General {category}\" holds packages tagged only \"{category}\" - it's often the largest group, and this is a grouping aid, not a strict taxonomy." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "У Homebrew нет официальных подкатегорий, поэтому это группировка по совместной встречаемости: внутри «{category}» пакеты раскладываются по другим категориям, к которым они тоже относятся. Пакет с несколькими пересекающимися категориями попадает в каждую такую группу. «Общее: {category}» содержит пакеты, отмеченные только категорией «{category}». Это подсказка для навигации, а не строгая таксономия." + } + } + } + }, + "discover.subgroupsLabel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "About sub-categories" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "О подкатегориях" + } + } + } + }, + "discover.subgroupsTitle": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "How these sub-groups are built" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Как устроены эти подгруппы" + } + } + } + }, + "package.installTrendFor": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "{name} install trend" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Динамика установок {name}" + } + } + } + }, + "package.openToken": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Open {token}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открыть {token}" + } + } + } + }, + "recovery.reason.alreadyInstalled": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "{name} is already installed outside Homebrew." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "{name} уже установлен вне Homebrew." + } + } + } + }, + "recovery.reason.stillRequired": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "{name} is still required by another installed package." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "{name} ещё нужен другому установленному пакету." + } + } + } + }, + "vulnerabilities.scanFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vulnerability scan failed" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось выполнить проверку уязвимостей" + } + } + } + }, + "vulnerabilities.scanOneFailed": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Vulnerability scan failed for {name}" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Не удалось проверить уязвимости для {name}" + } + } + } + }, + "settings.github.corruptSettings": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Settings file unreadable - visit the Network section to reset." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Файл настроек не читается — откройте раздел «Сеть», чтобы сбросить настройки." + } + } + } + }, + "settings.github.githubUserFallback": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "github user" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "пользователь GitHub" + } + } + } + }, + "settings.github.privacy.afterStruct": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " view crosses the IPC boundary." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "." + } + } + } + }, + "settings.github.privacy.beforeStruct": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "brew-browser stores your token in {keyringName}. The token is never sent over IPC to the renderer, never written to disk, and never logged. Only the derived " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "brew-browser хранит токен в {keyringName}. Токен никогда не отправляется в renderer-процесс по IPC, никогда не записывается на диск и никогда не попадает в логи. Через IPC передаётся только производное представление " + } + } + } + }, + "settings.github.scopes.afterNotifications": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " to enable watching. No private-repo access, no email read, no admin." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " для отслеживания. Доступа к приватным репозиториям, email и администрированию нет." + } + } + } + }, + "settings.github.scopes.afterPublicRepo": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " to enable starring and issue filing, and " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " для звёзд и создания issue, а также " + } + } + } + }, + "settings.github.scopes.afterReadUser": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": " to display your username, " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": " для отображения имени пользователя, " + } + } + } + }, + "settings.github.scopes.beforeReadUser": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Sign-in is optional. The minimum scopes requested are " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Вход необязателен. Запрашиваются минимальные разрешения: " + } + } + } + }, + "settings.github.signInHint": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Opens GitHub's standard \"Authorize app\" flow in your browser. No password is ever entered into brew-browser. This is required to Star, Watch, and file issues on GitHub with brew-browser." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Открывает стандартный поток GitHub «Authorize app» в браузере. Пароль никогда не вводится в brew-browser. Вход нужен, чтобы ставить звёзды, отслеживать репозитории и создавать issue в GitHub из brew-browser." + } + } + } + }, + "settings.github.signInPurpose": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "What sign-in is used for" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Для чего используется вход" + } + } + } + }, + "settings.github.signedInAs": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Signed in as" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Выполнен вход как" + } + } + } + }, + "settings.github.statsHint.afterHost": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "." + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "." + } + } + } + }, + "settings.github.statsHint.beforeHost": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Show repo stars, forks, and last release for any package whose homepage is a GitHub URL. brew-browser fetches public repo metadata from " + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Показывает звёзды, форки и последний релиз для пакетов, у которых домашняя страница ведёт на GitHub. brew-browser загружает публичные метаданные репозитория из " + } + } + } + }, + "settings.github.scopesLabel": { + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Scopes" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Разрешения" + } + } + } + } + }, + "version": "1.0" +} diff --git a/native/Sources/BrewBrowserKit/Resources/en.lproj/Localizable.strings b/native/Sources/BrewBrowserKit/Resources/en.lproj/Localizable.strings new file mode 100644 index 0000000..0696150 --- /dev/null +++ b/native/Sources/BrewBrowserKit/Resources/en.lproj/Localizable.strings @@ -0,0 +1,830 @@ +"AI & ML" = "AI & ML"; +"Browsers" = "Browsers"; +"Cloud & DevOps" = "Cloud & DevOps"; +"Communication" = "Communication"; +"Data" = "Data"; +"Developer Tools" = "Developer Tools"; +"Editors & IDEs" = "Editors & IDEs"; +"Education" = "Education"; +"Games & Entertainment" = "Games & Entertainment"; +"Graphics & Design" = "Graphics & Design"; +"Music" = "Music"; +"Office & Docs" = "Office & Docs"; +"Productivity" = "Productivity"; +"System Utilities" = "System Utilities"; +"Video & Audio" = "Video & Audio"; +"Writing" = "Writing"; +"Uncategorized" = "Uncategorized"; +"app.controls" = "App controls"; +"chrome.github.connectionStatus" = "GitHub connection status"; +"chrome.donate.aria" = "Donate to brew-browser on GitHub Sponsors"; +"chrome.donate.title" = "Donate via GitHub Sponsors"; +"command.openActivity" = "Open Activity"; +"command.openDiscover" = "Open Discover"; +"command.openLibrary" = "Open Library"; +"command.openSnapshots" = "Open Snapshots"; +"command.openTrending" = "Open Trending"; +"command.refreshLibrary" = "Refresh Library"; +"command.refreshSnapshots" = "Refresh Snapshots"; +"command.refreshTrending" = "Refresh Trending"; +"command.toggleActivityDrawer" = "Toggle Activity drawer"; +"github.connectedAs" = "GitHub: connected as @{username}"; +"github.scopeIncomplete" = "GitHub: signed in, but scope incomplete — click to fix"; +"keyboard.close" = "close"; +"keyboard.navigate" = "navigate"; +"keyboard.open" = "open"; +"language.english" = "English"; +"language.hint.reload" = "Changing the language reloads brew-browser."; +"language.label" = "Language"; +"language.russian" = "Russian"; +"language.system" = "System language"; +"nav.activity" = "Activity"; +"nav.dashboard" = "Dashboard"; +"nav.discover" = "Discover"; +"nav.library" = "Library"; +"nav.primary" = "Primary navigation"; +"nav.services" = "Services"; +"nav.snapshots" = "Snapshots"; +"nav.trending" = "Trending"; +"nav.showSidebar" = "Show sidebar"; +"nav.hideSidebar" = "Hide sidebar"; +"package.installed" = "installed"; +"package.kind.cask" = "cask"; +"package.kind.formula" = "formula"; +"palette.empty" = "No results."; +"palette.group.actions" = "Actions"; +"palette.group.commands" = "Commands"; +"palette.group.index" = "Index"; +"palette.group.installed" = "Installed"; +"palette.results" = "Command palette results"; +"palette.search.placeholder" = "Type a command, package, or section."; +"palette.searchLabel" = "Command palette search"; +"palette.title" = "Command palette"; +"palette.group.view" = "View"; +"search.allResultsInDiscover" = "See all results in Discover →"; +"search.clear" = "Clear"; +"search.clearSearch" = "Clear search"; +"search.noMatches" = "No matches for \"{query}\"."; +"search.packages" = "Search packages"; +"search.packagesPlaceholder" = "Search packages…"; +"search.results" = "Search results"; +"search.searching" = "Searching…"; +"settings.open" = "Open Settings"; +"settings.close" = "Close Settings"; +"settings.paneLabel" = "Settings — {section}"; +"settings.sections" = "Settings sections"; +"settings.section.about" = "About"; +"settings.section.activity" = "Activity"; +"settings.section.appearance" = "Appearance"; +"settings.section.brew" = "Brew"; +"settings.section.github" = "GitHub"; +"settings.section.network" = "Network"; +"settings.shortcutTitle" = "Settings (⌘,)"; +"settings.title" = "Settings"; +"theme.change" = "Change theme"; +"theme.dark" = "Dark"; +"theme.light" = "Light"; +"theme.title" = "Theme: {theme}"; +"theme.system" = "System"; +"trending.window.30d" = "30d"; +"trending.window.90d" = "90d"; +"trending.window.365d" = "365d"; +"updates.available" = "Update available"; +"updates.availableLabel" = "Update available: brew-browser {version}. Click to open Settings."; +"updates.dismissVersion" = "Dismiss update notification for v{version}"; +"updates.installing" = "Installing update"; +"updates.skipVersion" = "Skip this version"; +"updates.versionAvailable" = "brew-browser {version} is available"; +"vulnerability.packages" = "{count} vulnerable packages"; +"About" = "About"; +"About Brew Browser" = "About Brew Browser"; +"Activity" = "Activity"; +"Actions" = "Actions"; +"Advanced" = "Advanced"; +"AI features" = "AI features"; +"All" = "All"; +"All Categories" = "All Categories"; +"All current" = "All current"; +"Analytics" = "Analytics"; +"Appearance" = "Appearance"; +"App version" = "App version"; +"Autoremove" = "Autoremove"; +"Autoremove unused dependencies" = "Autoremove unused dependencies"; +"Auto-check daily" = "Auto-check daily"; +"Body (Markdown)" = "Body (Markdown)"; +"Brew" = "Brew"; +"Browse" = "Browse"; +"Cancel" = "Cancel"; +"Casks" = "Casks"; +"Casks (Caskroom)" = "Casks (Caskroom)"; +"Caveats" = "Caveats"; +"Check for updates now" = "Check for updates now"; +"Check now" = "Check now"; +"Choose packages to upgrade" = "Choose packages to upgrade"; +"Choose…" = "Choose…"; +"Categories unavailable" = "Categories unavailable"; +"Categories" = "Categories"; +"Clear" = "Clear"; +"Clear completed" = "Clear completed"; +"Clear Finished" = "Clear Finished"; +"Clear categories" = "Clear categories"; +"Clear category filter" = "Clear category filter"; +"Clear filter" = "Clear filter"; +"Click the code to copy it." = "Click the code to copy it."; +"Close" = "Close"; +"Command Palette…" = "Command Palette…"; +"Composition" = "Composition"; +"Confirm before uninstall / zap" = "Confirm before uninstall / zap"; +"Confirmations" = "Confirmations"; +"Contacting GitHub…" = "Contacting GitHub…"; +"Create" = "Create"; +"Dashboard" = "Dashboard"; +"Dark" = "Dark"; +"Default landing" = "Default landing"; +"Delete" = "Delete"; +"Dependency" = "Dependency"; +"Description" = "Description"; +"Download cache" = "Download cache"; +"Deselect all" = "Deselect all"; +"Discover" = "Discover"; +"Dismiss" = "Dismiss"; +"Dismiss for this session" = "Dismiss for this session"; +"Donate to the project" = "Donate to the project"; +"Enter this code:" = "Enter this code:"; +"Esc" = "Esc"; +"Export…" = "Export…"; +"Exposure" = "Exposure"; +"Fetch latest categories & descriptions" = "Fetch latest categories & descriptions"; +"Fetch trending history" = "Fetch trending history"; +"File issue" = "File issue"; +"File an issue" = "File an issue"; +"Filter" = "Filter"; +"Formulae" = "Formulae"; +"Formulae (Cellar)" = "Formulae (Cellar)"; +"GitHub" = "GitHub"; +"Greedy upgrades (include self-updating casks)" = "Greedy upgrades (include self-updating casks)"; +"Homebrew" = "Homebrew"; +"Icon source" = "Icon source"; +"Import…" = "Import…"; +"Install" = "Install"; +"Install brew-vulns" = "Install brew-vulns"; +"Install complete. Relaunch to use the new version." = "Install complete. Relaunch to use the new version."; +"Install trend" = "Install trend"; +"Installed" = "Installed"; +"Installed only" = "Installed only"; +"Installing…" = "Installing…"; +"instead" = "instead"; +"Keep last N completed jobs" = "Keep last N completed jobs"; +"Language" = "Language"; +"Labels" = "Labels"; +"Latest" = "Latest"; +"Library" = "Library"; +"License" = "License"; +"License mismatch" = "License mismatch"; +"Light" = "Light"; +"Lines per job" = "Lines per job"; +"Loading GitHub stats…" = "Loading GitHub stats…"; +"Loading settings…" = "Loading settings…"; +"Loading sign-in status…" = "Loading sign-in status…"; +"Logs (var/log)" = "Logs (var/log)"; +"Manual" = "Manual"; +"Name" = "Name"; +"Network" = "Network"; +"New Snapshot" = "New Snapshot"; +"No beta channel in this release." = "No beta channel in this release."; +"No background services" = "No background services"; +"No known vulnerabilities." = "No known vulnerabilities."; +"No known vulnerabilities" = "No known vulnerabilities"; +"No outdated packages." = "No outdated packages."; +"No results." = "No results."; +"No snapshots yet" = "No snapshots yet"; +"Not enough data yet" = "Not enough data yet"; +"Not scanned yet." = "Not scanned yet."; +"Offline Mode" = "Offline Mode"; +"Off" = "Off"; +"OK" = "OK"; +"Open in browser" = "Open in browser"; +"Open in Finder" = "Open in Finder"; +"Outbound paths (read-only)" = "Outbound paths (read-only)"; +"Package detail" = "Package detail"; +"Patched in" = "Patched in"; +"Pinned" = "Pinned"; +"Pinned packages are not upgraded by brew. Unpin from the package detail page." = "Pinned packages are not upgraded by brew. Unpin from the package detail page."; +"Recent" = "Recent"; +"RECENT" = "RECENT"; +"Refresh" = "Refresh"; +"Refresh from brew.sh →" = "Refresh from brew.sh →"; +"Refreshing…" = "Refreshing…"; +"Remove" = "Remove"; +"Repository" = "Repository"; +"Reset to defaults" = "Reset to defaults"; +"Resize package detail panel" = "Resize package detail panel"; +"Restore" = "Restore"; +"Retry" = "Retry"; +"Re-scan all" = "Re-scan all"; +"Scan all packages" = "Scan all packages"; +"Scan installed packages for known vulnerabilities" = "Scan installed packages for known vulnerabilities"; +"Scan now" = "Scan now"; +"Scanning all…" = "Scanning all…"; +"Scanning…" = "Scanning…"; +"Scrub — also remove the latest versions' cached downloads (more aggressive)" = "Scrub — also remove the latest versions' cached downloads (more aggressive)"; +"Search" = "Search"; +"Search packages…" = "Search packages…"; +"Searching…" = "Searching…"; +"Security" = "Security"; +"Select all" = "Select all"; +"Send Homebrew install analytics" = "Send Homebrew install analytics"; +"Service" = "Service"; +"Services" = "Services"; +"Settings" = "Settings"; +"Settings file is unreadable." = "Settings file is unreadable."; +"Show GitHub stats on package pages" = "Show GitHub stats on package pages"; +"Show sidebar" = "Show sidebar"; +"Hide sidebar" = "Hide sidebar"; +"Sign in" = "Sign in"; +"Sign in to GitHub" = "Sign in to GitHub"; +"Sign in with GitHub" = "Sign in with GitHub"; +"Sign out" = "Sign out"; +"Sign-in denied." = "Sign-in denied."; +"Similar packages" = "Similar packages"; +"Size" = "Size"; +"Snapshot" = "Snapshot"; +"Snapshots" = "Snapshots"; +"Stable" = "Stable"; +"Start" = "Start"; +"Stop" = "Stop"; +"Storage" = "Storage"; +"Status" = "Status"; +"Submit" = "Submit"; +"Support brew-browser via GitHub Sponsors" = "Support brew-browser via GitHub Sponsors"; +"System" = "System"; +"System language" = "System language"; +"Tags" = "Tags"; +"Tap" = "Tap"; +"Terminal" = "Terminal"; +"Theme" = "Theme"; +"Title" = "Title"; +"Token" = "Token"; +"Top categories in your library" = "Top categories in your library"; +"Trending" = "Trending"; +"Trending cache TTL" = "Trending cache TTL"; +"Try again" = "Try again"; +"Type" = "Type"; +"Type a command, package, or section." = "Type a command, package, or section."; +"Update available" = "Update available"; +"Update channel" = "Update channel"; +"Updates" = "Updates"; +"Updates available" = "Updates available"; +"Vulnerable" = "Vulnerable"; +"Velocity" = "Velocity"; +"Upgrade" = "Upgrade"; +"Upgrade all" = "Upgrade all"; +"Upgrade available" = "Upgrade available"; +"Upgrade to fix" = "Upgrade to fix"; +"Version" = "Version"; +"Verbose — list every file removed" = "Verbose — list every file removed"; +"View vulnerable packages →" = "View vulnerable packages →"; +"Waiting for authorization…" = "Waiting for authorization…"; +"Watch" = "Watch"; +"Watching" = "Watching"; +"Why install this?" = "Why install this?"; +"Window" = "Window"; +"Window vibrancy" = "Window vibrancy"; +"— all enrichment is baked into the app binary." = "— all enrichment is baked into the app binary."; +"Zero LLM calls are made from your machine" = "Zero LLM calls are made from your machine"; +"Zero telemetry. Zero accounts." = "Zero telemetry. Zero accounts."; +"built with" = "built with"; +"close" = "close"; +"close Esc" = "close Esc"; +"dependency" = "dependency"; +"formula" = "formula"; +"cask" = "cask"; +"installed" = "installed"; +"not present" = "not present"; +"pinned" = "pinned"; +"source" = "source"; +"unavailable" = "unavailable"; +"working…" = "working…"; +"Adopt existing" = "Adopt existing"; +"Backend not available" = "Backend not available"; +"Catalog refresh failed" = "Catalog refresh failed"; +"Change theme" = "Change theme"; +"Changing the language reloads brew-browser." = "Changing the language reloads brew-browser."; +"Cleanup failed" = "Cleanup failed"; +"Clean up cache…" = "Clean up cache…"; +"Clean up" = "Clean up"; +"Cleaning…" = "Cleaning…"; +"Cleaning up Homebrew cache" = "Cleaning up Homebrew cache"; +"Command palette" = "Command palette"; +"Command palette search" = "Command palette search"; +"Command palette results" = "Command palette results"; +"Couldn't change brew analytics" = "Couldn't change brew analytics"; +"Couldn't load services" = "Couldn't load services"; +"Couldn't load snapshots" = "Couldn't load snapshots"; +"Couldn't load packages." = "Couldn't load packages."; +"Couldn't open in file manager" = "Couldn't open in file manager"; +"Couldn't reveal in Finder" = "Couldn't reveal in Finder"; +"Disk probe failed" = "Disk probe failed"; +"Donate via GitHub Sponsors" = "Donate via GitHub Sponsors"; +"Donate to brew-browser on GitHub Sponsors" = "Donate to brew-browser on GitHub Sponsors"; +"Everything is up to date" = "Everything is up to date"; +"English" = "English"; +"Follows the macOS theme when set to System." = "Follows the macOS theme when set to System."; +"Failed to load packages" = "Failed to load packages"; +"Force remove" = "Force remove"; +"GitHub connection status" = "GitHub connection status"; +"Homebrew analytics disabled" = "Homebrew analytics disabled"; +"Homebrew analytics enabled" = "Homebrew analytics enabled"; +"Homebrew was not found on this Mac." = "Homebrew was not found on this Mac."; +"Index" = "Index"; +"Installed as a dependency, not requested directly" = "Installed as a dependency, not requested directly"; +"Invalid URL" = "Invalid URL"; +"Loading your packages…" = "Loading your packages…"; +"Measuring disk usage…" = "Measuring disk usage…"; +"Nothing's run yet." = "Nothing's run yet."; +"Open Activity" = "Open Activity"; +"Open Discover" = "Open Discover"; +"Open Library" = "Open Library"; +"Open Settings" = "Open Settings"; +"Open Snapshots" = "Open Snapshots"; +"Open Trending" = "Open Trending"; +"Open Library, filter outdated" = "Open Library, filter outdated"; +"Other" = "Other"; +"Pick which packages to upgrade" = "Pick which packages to upgrade"; +"Refresh Library" = "Refresh Library"; +"Refresh Snapshots" = "Refresh Snapshots"; +"Refresh Trending" = "Refresh Trending"; +"Refresh disk usage" = "Refresh disk usage"; +"Refreshed" = "Refreshed"; +"Report an issue on GitHub" = "Report an issue on GitHub"; +"Report to brew-browser" = "Report to brew-browser"; +"Remove from history" = "Remove from history"; +"Remove it anyway, ignoring packages that depend on it." = "Remove it anyway, ignoring packages that depend on it."; +"Remove unused dependencies?" = "Remove unused dependencies?"; +"Removing unused dependencies" = "Removing unused dependencies"; +"Removing…" = "Removing…"; +"Replace the existing copy with a fresh Homebrew install." = "Replace the existing copy with a fresh Homebrew install."; +"Run brew doctor" = "Run brew doctor"; +"Run brew doctor and stream the diagnostics into Activity" = "Run brew doctor and stream the diagnostics into Activity"; +"Run brew update, refresh the catalog from formulae.brew.sh, and reload your installed list" = "Run brew update, refresh the catalog from formulae.brew.sh, and reload your installed list"; +"Russian" = "Russian"; +"Search packages" = "Search packages"; +"Search results" = "Search results"; +"See the Activity drawer." = "See the Activity drawer."; +"Settings (⌘,)" = "Settings (⌘,)"; +"Settings sections" = "Settings sections"; +"Submit issue" = "Submit issue"; +"This will install packages from the snapshot. Existing packages are skipped." = "This will install packages from the snapshot. Existing packages are skipped."; +"Toggle Activity drawer" = "Toggle Activity drawer"; +"Upgrade-all failed" = "Upgrade-all failed"; +"Upgrading…" = "Upgrading…"; +"When off, only Homebrew's native metadata appears. Categories (sidebar tile grid, donut chart, chip filters) are also hidden because they're AI-generated." = "When off, only Homebrew's native metadata appears. Categories (sidebar tile grid, donut chart, chip filters) are also hidden because they're AI-generated."; +"When on, brew-browser shows extra metadata generated at build time by AI: friendly names, expanded descriptions, use cases, similar package suggestions, and category tags." = "When on, brew-browser shows extra metadata generated at build time by AI: friendly names, expanded descriptions, use cases, similar package suggestions, and category tags."; +"Which section opens when you launch brew-browser." = "Which section opens when you launch brew-browser."; +"Requires app restart to take effect. The default (HudWindow) matches the rest of macOS." = "Requires app restart to take effect. The default (HudWindow) matches the rest of macOS."; +"all packages" = "all packages"; +"brew commands kicked off from here will show up in this list." = "brew commands kicked off from here will show up in this list."; +"disabled" = "disabled"; +"Advisory" = "Advisory"; +"Catalog unavailable" = "Catalog unavailable"; +"Check this package for known vulnerabilities." = "Check this package for known vulnerabilities."; +"Commands" = "Commands"; +"Couldn't load packages" = "Couldn't load packages"; +"Couldn't load trending" = "Couldn't load trending"; +"Copy" = "Copy"; +"Copy the command to the clipboard" = "Copy the command to the clipboard"; +"Deprecated" = "Deprecated"; +"Disabled" = "Disabled"; +"Disabled by Offline Mode" = "Disabled by Offline Mode"; +"Blocked by Offline Mode. Disable in Settings → Network." = "Blocked by Offline Mode. Disable in Settings → Network."; +"Fetching install counts from formulae.brew.sh…" = "Fetching install counts from formulae.brew.sh…"; +"Homebrew isn't installed" = "Homebrew isn't installed"; +"Install Homebrew" = "Install Homebrew"; +"Install `brew vulns` to scan this package for known vulnerabilities." = "Install `brew vulns` to scan this package for known vulnerabilities."; +"Install the Xcode Command Line Tools" = "Install the Xcode Command Line Tools"; +"Installs" = "Installs"; +"Installs, upgrades, uninstalls, and updates show up here." = "Installs, upgrades, uninstalls, and updates show up here."; +"Loading snapshots…" = "Loading snapshots…"; +"Loading the Homebrew catalog…" = "Loading the Homebrew catalog…"; +"Members also categorized here" = "Members also categorized here"; +"Members of this category with no other category" = "Members of this category with no other category"; +"No activity yet" = "No activity yet"; +"No packages" = "No packages"; +"No results" = "No results"; +"No summary available — view details" = "No summary available — view details"; +"Not installed" = "Not installed"; +"Offline Mode is on — trending needs formulae.brew.sh." = "Offline Mode is on — trending needs formulae.brew.sh."; +"Open Dashboard" = "Open Dashboard"; +"Open Services" = "Open Services"; +"Open Terminal" = "Open Terminal"; +"Outdated" = "Outdated"; +"Paste the official install command (from brew.sh) into Terminal:" = "Paste the official install command (from brew.sh) into Terminal:"; +"Reading your Homebrew install…" = "Reading your Homebrew install…"; +"Restart" = "Restart"; +"Runs brew vulns across your whole install — it can't scan a single package" = "Runs brew vulns across your whole install — it can't scan a single package"; +"Save your current setup so you can restore it on another Mac. Snapshots live in ~/Library/Application Support/brew-browser/brewfiles/ — findable outside the app too." = "Save your current setup so you can restore it on another Mac. Snapshots live in ~/Library/Application Support/brew-browser/brewfiles/ — findable outside the app too."; +"Scanning all packages…" = "Scanning all packages…"; +"Sign in to GitHub happens on first action." = "Sign in to GitHub happens on first action."; +"Sign in to GitHub to star this repository" = "Sign in to GitHub to star this repository"; +"Sign in to GitHub to watch this repository" = "Sign in to GitHub to watch this repository"; +"Snapshot error" = "Snapshot error"; +"Star" = "Star"; +"Star this repository" = "Star this repository"; +"Starred" = "Starred"; +"Stop watching" = "Stop watching"; +"Stored in ~/Library/Application Support/brew-browser/brewfiles/" = "Stored in ~/Library/Application Support/brew-browser/brewfiles/"; +"The Brewfile will be removed from disk. This cannot be undone." = "The Brewfile will be removed from disk. This cannot be undone."; +"The Homebrew installer needs them. Run this in Terminal and follow the prompt:" = "The Homebrew installer needs them. Run this in Terminal and follow the prompt:"; +"The bundled package catalog couldn't be loaded." = "The bundled package catalog couldn't be loaded."; +"The brew-vulns subcommand isn't installed." = "The brew-vulns subcommand isn't installed."; +"settings.vulnerabilities.cleanResult" = "No known vulnerabilities across your installed packages."; +"settings.vulnerabilities.helperInstalledTitle" = "brew-vulns installed"; +"settings.vulnerabilities.initialScanBody" = "Running an initial scan now…"; +"settings.vulnerabilities.installFailedTitle" = "Couldn't install brew-vulns"; +"settings.vulnerabilities.installPrompt" = "Vulnerability scanning needs the official brew vulns subcommand. Install it now? This runs brew install homebrew/brew-vulns/brew-vulns."; +"settings.vulnerabilities.lastScan" = "Last scan"; +"settings.vulnerabilities.offlineHint" = "Offline Mode is on — vulnerability scanning is suppressed even if this toggle is on. Disable Offline Mode above to enable scanning."; +"settings.vulnerabilities.privacyHint" = "Opt-in, off by default. When on, brew-browser shells out to the official brew vulns subcommand (Homebrew project), which queries OSV.dev (operated by Google) for known vulnerabilities affecting your installed formulae. If you're also signed in to GitHub, individual GHSA-IDs are enriched with details from api.github.com. Findings are cached locally; no package list leaves your machine except the queries brew vulns itself makes to OSV."; +"settings.vulnerabilities.scanAllTitle" = "Run brew vulns against every installed formula"; +"This installs packages from the snapshot. Existing packages are skipped." = "This installs packages from the snapshot. Existing packages are skipped."; +"Use" = "Use"; +"User" = "User"; +"Uninstall" = "Uninstall"; +"Unstar this repository" = "Unstar this repository"; +"Vulnerability Scanning" = "Vulnerability Scanning"; +"Waiting for Homebrew… the app continues automatically once it's installed." = "Waiting for Homebrew… the app continues automatically once it's installed."; +"Watch for activity" = "Watch for activity"; +"Working… (see Activity)" = "Working… (see Activity)"; +"Your browser opened to GitHub's device page. Enter this code (it's copied to your clipboard):" = "Your browser opened to GitHub's device page. Enter this code (it's copied to your clipboard):"; +"brew-browser hit an unexpected error running this command." = "brew-browser hit an unexpected error running this command."; +"brew-browser is a window onto Homebrew — install it and the app takes it from there." = "brew-browser is a window onto Homebrew — install it and the app takes it from there."; +"formulae.brew.sh returned no entries." = "formulae.brew.sh returned no entries."; +"instead." = "instead."; +"Already running" = "Already running"; +"App Store Version Detected" = "App Store Version Detected"; +"Cancel sign in" = "Cancel sign in"; +"Click to copy" = "Click to copy"; +"Code copied to clipboard" = "Code copied to clipboard"; +"Code expired. Please try again." = "Code expired. Please try again."; +"Couldn't copy code" = "Couldn't copy code"; +"Delete failed" = "Delete failed"; +"Deleted snapshot" = "Deleted snapshot"; +"Export failed" = "Export failed"; +"Exported snapshot" = "Exported snapshot"; +"Import failed" = "Import failed"; +"Imported snapshot" = "Imported snapshot"; +"Install & Override" = "Install & Override"; +"Installation Blocked" = "Installation Blocked"; +"Loading brew services…" = "Loading brew services…"; +"No background services." = "No background services."; +"Install something like postgresql, redis, or nginx and they'll show up here." = "Install something like postgresql, redis, or nginx and they'll show up here."; +"Refresh services" = "Refresh services"; +"Restore complete" = "Restore complete"; +"Restore failed" = "Restore failed"; +"Snapshot failed" = "Snapshot failed"; +"Snapshot saved" = "Snapshot saved"; +"Start service" = "Start service"; +"Stop service" = "Stop service"; +"Restart service" = "Restart service"; +"Not running" = "Not running"; +"Brew services" = "Brew services"; +"Open package details" = "Open package details"; +"Save your current setup so you can restore it on another machine. Snapshots live in ~/.local/share/brew-browser/brewfiles/ — findable outside the app too." = "Save your current setup so you can restore it on another machine. Snapshots live in ~/.local/share/brew-browser/brewfiles/ — findable outside the app too."; +"This will remove the package from your system." = "This will remove the package from your system."; +"Overwrite manual installation?" = "Overwrite manual installation?"; +"An existing version was found in your Applications folder. If you proceed, Homebrew will force-install and overwrite the existing bundle." = "An existing version was found in your Applications folder. If you proceed, Homebrew will force-install and overwrite the existing bundle."; +"An existing App Store version was found in your Applications folder." = "An existing App Store version was found in your Applications folder."; +"App Store bundles are system-protected and cannot be overwritten by Homebrew." = "App Store bundles are system-protected and cannot be overwritten by Homebrew."; +"Resolution: delete the App Store version from /Applications, then install it again via Homebrew." = "Resolution: delete the App Store version from /Applications, then install it again via Homebrew."; +"Use the Sign in with GitHub button in Settings." = "Use the Sign in with GitHub button in Settings."; +"Re-authorize" = "Re-authorize"; +"Upgrade failed" = "Upgrade failed"; +"Upgraded packages" = "Upgraded packages"; +"Watching package" = "Watching package"; +"Stopped watching package" = "Stopped watching package"; +"Starred package" = "Starred package"; +"Unstarred package" = "Unstarred package"; +"Signed in as GitHub user." = "Signed in as GitHub user."; +"settings.activity.rangeDefault" = "Range {min}-{max}. Default {default}."; +"settings.activity.retentionNote" = "These limits apply to future job persistence. Existing retained data is not trimmed retroactively."; +"settings.brew.analyticsDesc.beforeCommand" = "Homebrew sends anonymous install analytics to formulae.brew.sh by default. This toggle flips Homebrew's setting (the same as running "; +"settings.brew.analyticsDesc.afterCommand" = " in your terminal)."; +"settings.brew.analyticsToggle" = "Send Homebrew install analytics"; +"settings.brew.confirmationsDesc" = "Destructive actions (Uninstall, Zap, Delete Brewfile) ask before proceeding. Power users can turn this off once they're sure."; +"settings.brew.advancedDesc.beforeCommand" = "Extra "; +"settings.brew.advancedDesc.afterCommand" = " options for power users."; +"settings.brew.greedyDesc.beforeFlag" = "Adds "; +"settings.brew.greedyDesc.between" = " to "; +"settings.brew.greedyDesc.afterCommand" = " so casks that update themselves (like Chrome) are upgraded too. Off by default - greedy can churn apps that manage their own updates."; +"settings.brew.autoremoveDesc.beforeCommand" = "Runs "; +"settings.brew.autoremoveDesc.afterCommand" = " to uninstall formulae that were installed only as dependencies and are no longer needed by anything."; +"settings.brew.autoremoveConfirm.beforeCommand" = "This runs "; +"settings.brew.autoremoveConfirm.afterCommand" = ", which uninstalls formulae that were installed only as dependencies and are no longer required by anything else."; +"settings.liveEnrichment.title" = "Live category & description updates"; +"settings.liveEnrichment.hint.beforeHost" = "brew-browser ships with a built-in set of AI categories and descriptions. When on, it refreshes them from "; +"settings.liveEnrichment.hint.afterHost" = ": a tiny version check on refresh, the full category list when it's newer, and a per-package description when you open that package's detail. Only the package name you're viewing is sent (one HTTP GET); no IP is logged at the server, no cookies. Same first-party host as Enhanced Trending - a distinct trust boundary from the always-on Homebrew paths. Requires AI Features on."; +"settings.liveEnrichment.offline" = "Offline Mode is on - this toggle is locked off. Turn Offline Mode off above to enable live updates."; +"settings.trendingHistory.title" = "Enhanced Trending History"; +"settings.trendingHistory.hint.beforeHost" = "When on, brew-browser fetches per-package historical install trends from "; +"settings.trendingHistory.hint.afterHost" = " to power per-row sparklines on the Trending tab and a chart on each package's detail panel. Only the package name you're viewing is sent (one HTTP GET per package); no IP is logged at the server, no cookies, no fingerprinting. The endpoint is operated by the brew-browser project - a distinct trust boundary from the always-on Homebrew analytics paths above."; +"settings.trendingHistory.offline" = "Offline Mode is on - this toggle is locked off. Turn Offline Mode off above to enable history fetching."; +"settings.about.zeroTelemetryBody" = "brew-browser does not collect telemetry. It does not phone home. It does not have user accounts. Every outbound network request is documented in Settings -> Network and the README, and only fires when you take an action that requires it."; +"about.closeAria" = "Close About"; +"about.tagline" = "A native macOS GUI for Homebrew."; +"about.creditsBody.beforeClaude" = " - the multi-agent toolkit that orchestrated the waves (Backend Architect, Frontend Developer, Security Engineer, Code Reviewer, Technical Writer, and friends), powered by "; +"about.creditsBody.afterClaude" = " in the terminal, running "; +"about.thanksLinux" = "Thanks also to the Homebrew project for the package data, every formula maintainer for their work, and Tauri for the native shell."; +"about.thanksMac" = "Thanks also to the Homebrew project for the package data, every formula and cask maintainer for their work, and Tauri for the native shell."; +"about.posture" = "Zero telemetry. Zero accounts. Every outbound network call is documented in Settings -> Network."; +"Repo" = "Repo"; +"Built with" = "Built with"; +"Open GitHub Sponsors in your browser" = "Open GitHub Sponsors in your browser"; +"Close detail panel" = "Close detail panel"; +"Loading package detail…" = "Loading package detail…"; +"Couldn't load detail" = "Couldn't load detail"; +"Installed by User" = "Installed by User"; +"On request" = "On request"; +"Total on-disk size of the installed keg (all versions)." = "Total on-disk size of the installed keg (all versions)."; +"About categories" = "About categories"; +"About this package's categories" = "About this package's categories"; +"About tags" = "About tags"; +"About these tags" = "About these tags"; +"About this summary" = "About this summary"; +"detail.aboutCategories.body" = "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if a category looks off and we'll fix it in the next release."; +"detail.aboutTags.body" = "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if a tag looks off and we'll fix it in the next release."; +"detail.aboutSummary.body" = "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if the summary looks off and we'll fix it in the next release."; +"No longer available via Homebrew." = "No longer available via Homebrew."; +"May be removed in a future Homebrew update." = "May be removed in a future Homebrew update."; +"Run brew upgrade for this package" = "Run brew upgrade for this package"; +"Open advisory" = "Open advisory"; +"Patched version" = "Patched version"; +"detail.trend.bootstrap" = "Bootstrap + daily snapshots - granularity grows over time"; +"detail.trend.daily" = "Daily install snapshots"; +"Use cases" = "Use cases"; +"About use cases" = "About use cases"; +"About these use cases" = "About these use cases"; +"detail.aboutUseCases.body" = "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if these use cases look off and we'll fix them in the next release."; +"About similar packages" = "About similar packages"; +"About these similar packages" = "About these similar packages"; +"detail.aboutSimilar.body" = "Generated offline at build time by Claude Haiku 4.5 - no network or LLM calls happen while you use brew-browser. Open an issue if these suggestions look off and we'll fix them in the next release."; +"GitHub repository statistics" = "GitHub repository statistics"; +"Couldn't load GitHub stats" = "Couldn't load GitHub stats"; +"Dependencies" = "Dependencies"; +"Required by" = "Required by"; +"About reverse dependencies" = "About reverse dependencies"; +"detail.aboutReverseDependencies.body" = "Packages in the bundled catalog that list this one as a dependency. Computed offline by inverting the catalog graph - no network or brew calls. Build-only and opt-in (recommended/optional) edges are labelled; everything else is a required runtime dependency."; +"Conflicts with" = "Conflicts with"; +"detail.action.reinstall" = "Reinstall"; +"detail.category.openTitle" = "Browse all packages in {category}"; +"detail.caskUnavailableLinux" = "Casks require macOS — this package can't be installed on Linux."; +"detail.deprecation.disabled.body" = "This package is disabled and is no longer available — it will be removed."; +"detail.deprecation.deprecated.body" = "This package is deprecated and scheduled for removal. It still installs for now."; +"detail.deprecation.disabledDate.format" = "Disabled: %@"; +"detail.deprecation.deprecatedDate.format" = "Deprecated: %@"; +"detail.deprecation.disabled.tooltip" = "Disabled — this package is no longer available and will be removed."; +"detail.deprecation.deprecated.tooltip" = "Deprecated — still installable, but scheduled for removal."; +"detail.deprecation.useInstead" = "Use instead:"; +"about.title" = "About Brew Browser"; +"action.cancel" = "Cancel"; +"action.close" = "Close"; +"action.deselectAll" = "Deselect all"; +"action.installing" = "Installing…"; +"action.refresh" = "Refresh"; +"action.retry" = "Retry"; +"action.selectAll" = "Select all"; +"activity.collapse" = "Collapse activity"; +"activity.copyOutput" = "Copy output"; +"activity.drawer.hide" = "Hide Activity drawer"; +"activity.drawer.show" = "Show Activity drawer"; +"activity.expand" = "Expand activity"; +"activity.keepJobs.format" = "Keep last %d completed jobs"; +"activity.limits.help" = "These limits apply to future saved jobs only. Existing history is not shortened retroactively."; +"activity.linesPerJob.format" = "Keep last %d log lines per job"; +"activity.notifyOnCompletion" = "Notify when jobs complete"; +"activity.notifyOnCompletion.help" = "Shows a macOS notification when a long-running brew job finishes."; +"chrome.donate.help" = "Support brew-browser via GitHub Sponsors"; +"chrome.donate.menu" = "Sponsor brew-browser…"; +"icon.source.faviconFrom" = "Favicon from {homepage}"; +"icon.source.homepage" = "homepage"; +"icon.source.installedApp" = "installed app"; +"icon.source.none" = "none"; +"enrichment.fetchLatest" = "Fetch latest categories & descriptions"; +"enrichment.fetchLatest.help" = "brew-browser ships with built-in AI categories and descriptions. When enabled, it checks for newer first-party enrichment data and fetches a package description when you open the package detail. Only the viewed package name is sent."; +"filter.all" = "All"; +"filter.dependency" = "Dependencies"; +"filter.manual" = "Manual"; +"filter.outdated" = "Outdated"; +"filter.vulnerable" = "Vulnerable"; +"github.connectedAs.format" = "GitHub: connected as @%@"; +"github.deviceFlowInstructions.format" = "Open %@ and enter this code. It has been copied to your clipboard."; +"github.keychain.help" = "The token is stored in macOS Keychain. Sign-in is optional and uses the minimum scopes needed for stars, watches, and issues."; +"github.noInstalledHomepages" = "None of your installed packages have a GitHub homepage."; +"github.signIn" = "Sign in"; +"github.signIn.help" = "Sign in with GitHub to star, watch, and file package metadata issues from brew-browser."; +"github.signInWithGitHub" = "Sign in with GitHub"; +"github.signOut" = "Sign out"; +"github.signedInAs" = "Signed in as"; +"github.signedInAsToast" = "Signed in as @{username}"; +"github.signedInToast" = "Signed in to GitHub"; +"github.signInDenied" = "GitHub sign-in denied"; +"github.signInExpired" = "Sign-in code expired"; +"github.signInExpiredBody" = "Try again."; +"github.stargazers" = "Stargazers"; +"github.forks" = "Forks"; +"github.licenseMismatchTitle" = "brew reports: {brew} · GitHub reports: {github}"; +"github.scopeRequired.title.format" = "%@ needs more access"; +"github.scopeRequired.body.format" = "Needs the \"%@\" GitHub permission. Click to grant it without signing out."; +"github.yourCode" = "Your code"; +"brew.status.checking" = "Checking Homebrew…"; +"brew.status.installed" = "Homebrew is installed."; +"brew.status.notFound" = "brew not found"; +"brew.status.notFoundOnPath" = "Homebrew not found on PATH."; +"brew.status.ready.format" = "Homebrew %@ · prefix %@"; +"brew.status.unknown" = "Homebrew status unknown"; +"brew.status.unknownWithError" = "Homebrew status unknown - {error}"; +"catalog.refresh.noWritableDir" = "Couldn't locate a writable catalog directory."; +"catalog.refresh.failed.format" = "Catalog refresh failed: %@"; +"catalog.refresh.malformedResponse" = "brew.sh returned an unexpected catalog format."; +"catalog.refresh.emptyResponse.format" = "brew.sh returned an empty catalog (formulae=%d, casks=%d)."; +"catalog.refresh.compression" = "Couldn't compress the refreshed catalog."; +"catalog.refresh.offlineBlocked" = "Offline mode is on — catalog refresh is blocked. Disable it in Settings → Network."; +"catalog.refresh.brewFailed" = "brew failed during refresh (exit {code})."; +"menu.go" = "Go"; +"package.kind.casks" = "Casks"; +"package.kind.formulae" = "Formulae"; +"package.pinned" = "Pinned"; +"palette.title.menu" = "Command Palette…"; +"security.brewVulnsInstalled" = "brew-vulns is installed"; +"security.brewVulnsMissing.help" = "Vulnerability scanning needs the official brew vulns subcommand."; +"security.installBrewVulns" = "Install brew-vulns"; +"security.offlineSuppressed" = "Offline Mode is on — vulnerability scanning is suppressed."; +"security.scanInstalled" = "Scan installed packages for known vulnerabilities"; +"security.scanInstalled.help" = "Opt-in, off by default. Runs the official brew vulns subcommand for installed formulae and stores findings locally."; +"security.viewVulnerablePackages" = "View vulnerable packages →"; +"detail.security.cleanStale" = "No advisories as of the last scan — re-scan to confirm"; +"detail.security.lastScan.format" = "Last scan: %@"; +"detail.security.notScanned.detail" = "Not scanned yet. Scanning checks every installed package — brew vulns can't scan just one."; +"detail.security.openAdvisory.format" = "Open advisory: %@"; +"detail.security.patchedIn.format" = "Patched in %@"; +"detail.security.rescanHelp" = "Runs brew vulns across your whole install — it can't scan a single package"; +"onboarding.aria" = "Homebrew setup"; +"onboarding.title" = "Let's get Homebrew set up"; +"onboarding.body.mac" = "brew-browser is a friendly face for Homebrew, the package manager for your Mac. Install it once and your library appears here automatically — no relaunch needed."; +"onboarding.body.linux" = "brew-browser is a friendly face for Homebrew, which runs on Linux too. Install it once and your library appears here automatically — no relaunch needed."; +"onboarding.clt.before" = "Install the Xcode Command Line Tools first: run"; +"onboarding.clt.after" = "in Terminal and follow the prompts."; +"onboarding.linuxDeps.before" = "Install Homebrew's build dependencies first — on Debian/Ubuntu:"; +"onboarding.linuxDeps.after" = "other distros: see the \"Homebrew on Linux\" page at docs.brew.sh."; +"onboarding.installCopied" = "Install command copied to clipboard"; +"onboarding.copyFailed" = "Couldn't copy command"; +"onboarding.openTerminalFailed" = "Couldn't open Terminal"; +"onboarding.commandCopiedFallback" = "The install command was copied instead — paste it into any terminal."; +"onboarding.openTerminalOrCopyFailed" = "Couldn't open Terminal or copy the command"; +"services.alreadyRunning" = "Already running"; +"services.empty.body" = "Install something like postgresql, redis, or nginx and services will show up here."; +"services.empty.title" = "No background services"; +"services.loadFailed" = "Couldn't load services"; +"services.loading" = "Loading brew services…"; +"services.notRunning" = "Not running"; +"services.restart" = "Restart service"; +"services.start" = "Start service"; +"services.status.error" = "error"; +"services.status.notLoaded" = "not loaded"; +"services.status.running" = "running"; +"services.status.scheduled" = "scheduled"; +"services.status.stopped" = "stopped"; +"services.status.unknown" = "unknown"; +"services.stop" = "Stop service"; +"settings.appearance.aiFeatures" = "AI features"; +"settings.appearance.aiFeatures.help" = "When off, only Homebrew's native metadata appears. AI-generated categories and descriptions are hidden."; +"settings.appearance.defaultLanding" = "Default landing"; +"settings.brew.advanced" = "Advanced"; +"settings.brew.analytics" = "Analytics"; +"settings.brew.analytics.help" = "Homebrew sends anonymous install analytics to formulae.brew.sh by default. This changes Homebrew's own analytics preference."; +"settings.brew.autoremove" = "Autoremove"; +"settings.brew.autoremove.confirmMessage" = "This runs brew autoremove to delete formulae installed only as dependencies and no longer needed by any package."; +"settings.brew.autoremove.confirmTitle" = "Remove unused dependencies?"; +"settings.brew.autoremove.failedToRun" = "brew autoremove failed to run"; +"settings.brew.autoremove.help" = "Runs brew autoremove to remove formulae that were installed only as dependencies and are no longer needed."; +"settings.brew.autoremoveUnused" = "Autoremove unused dependencies"; +"settings.brew.confirmDestructive" = "Confirm before uninstall / zap"; +"settings.brew.confirmDestructive.help" = "Ask for confirmation before irreversible actions: uninstalling a package, zap cleanup, or deleting a Brewfile."; +"settings.brew.confirmations" = "Confirmations"; +"settings.brew.greedyUpgrades" = "Greedy upgrades"; +"settings.brew.greedyUpgrades.help" = "Include self-updating casks such as Chrome when running brew upgrade --greedy. Off by default."; +"settings.brew.sendAnalytics" = "Send Homebrew install analytics"; +"settings.fileUnreadable" = "Settings file is unreadable."; +"settings.github.showStats" = "Show GitHub stats on package pages"; +"settings.github.showStats.help" = "When signed in, brew-browser checks whether you have starred packages with GitHub homepages."; +"settings.network.caskIconFetching" = "Cask icon fetching"; +"settings.network.catalogAutoRefresh" = "Catalog auto-refresh"; +"settings.network.catalogStaleThreshold.format" = "Show stale catalog banner after %d days"; +"settings.network.offlineMode" = "Offline Mode"; +"settings.network.offlineMode.help" = "Blocks all brew-browser network calls. brew commands you start still run normally."; +"settings.network.offlineMode.on" = "Offline Mode is on"; +"settings.network.trendingTtl.format" = "Trending cache TTL: %d minutes"; +"settings.offlineToggleLocked" = "Offline Mode is on — this toggle is locked."; +"settings.option.daily" = "Daily"; +"settings.option.installedOnly" = "Installed only"; +"settings.option.off" = "Off"; +"settings.option.weekly" = "Weekly"; +"settings.resetToDefaults" = "Reset to defaults"; +"settings.section.security" = "Security"; +"settings.section.updates" = "Updates"; +"table.actions" = "Actions"; +"table.name" = "Name"; +"table.sortBy" = "Sort by {label}"; +"table.sortedAsc" = "Sorted by {label}, ascending. Click to reverse."; +"table.sortedDesc" = "Sorted by {label}, descending. Click to reverse."; +"table.status" = "Status"; +"table.user" = "User"; +"dashboard.refresh.brewUpdateFailed" = "brew update finished with errors"; +"dashboard.refresh.catalogFailed" = "Catalog refresh failed"; +"dashboard.refresh.failed" = "Refresh failed"; +"dashboard.refresh.successTitle" = "Refreshed"; +"dashboard.refresh.successBody" = "brew taps + catalog + installed list all current"; +"dashboard.doctor.failedToRun" = "brew doctor failed to run"; +"dashboard.upgradeAll.success" = "Upgraded {count} packages"; +"storage.refreshDiskUsage" = "Refresh disk usage"; +"storage.diskProbeFailed" = "Disk probe failed: {error}"; +"storage.measuringDiskUsage" = "Measuring disk usage… (du -sk on {prefix})"; +"storage.notPresent" = "not present"; +"storage.revealInFinder" = "Reveal {path} in Finder"; +"storage.showInFileManager" = "Show {path} in file manager"; +"storage.revealLabelInFinder" = "Reveal {label} in Finder"; +"storage.showLabelInFileManager" = "Show {label} in file manager"; +"storage.pathMissing" = "Path doesn't exist"; +"resize.panel" = "Resize panel"; +"resize.dragHint" = "Drag to resize · double-click to reset"; +"theme.cycle" = "Cycle theme"; +"theme.label" = "Theme"; +"trending.fetchHistory" = "Fetch trending history"; +"trending.fetchHistory.help" = "Downloads per-package trend history for sparklines and package detail charts."; +"trending.sparklineAria" = "Install trend sparkline"; +"trending.error.backend" = "Backend not available: {error}"; +"trending.error.failed" = "Failed to load trending: {error}"; +"trending.error.network" = "Couldn't reach formulae.brew.sh"; +"updates.autoCheck" = "Auto-check daily"; +"updates.autoCheck.help" = "Automatically check for brew-browser updates once a day."; +"updates.available.help" = "Click to open the updater."; +"updates.channel" = "Channel"; +"updates.channel.label" = "Update channel"; +"updates.channel.stable" = "Stable"; +"updates.check.help" = "Checks the Sparkle update feed for a newer brew-browser build."; +"updates.checkForUpdates" = "Check for Updates…"; +"updates.checkNow" = "Check now"; +"updates.lastChecked.format" = "Last checked: %@"; +"updates.never" = "Never"; +"updates.noBeta" = "No beta channel in this release."; +"updates.offlineBlocked" = "Offline Mode blocks update checks."; +"upgrade.choosePackages" = "Choose packages to upgrade"; +"upgrade.noOutdated" = "No outdated packages."; +"upgrade.pinned.help" = "Pinned packages are not upgraded by brew. Unpin from the package detail page."; +"deprecated" = "deprecated"; +"discover.activeFilters" = "Active category filters"; +"discover.catalogFetched" = "Fetched from formulae.brew.sh"; +"discover.catalogRefreshed" = "Catalog refreshed"; +"discover.categoryBrowsingHidden" = "Category browsing is hidden because AI features are turned off in Settings → Appearance."; +"discover.categoryTileAria" = "{category} - {count} packages"; +"discover.deprecatedDefault" = "Deprecated - may be removed in a future Homebrew update."; +"discover.deprecatedReason" = "Deprecated: {reason}"; +"discover.disabledDefault" = "Disabled - no longer available via Homebrew."; +"discover.disabledReason" = "Disabled: {reason}"; +"discover.emptyFilter" = "No packages match this filter."; +"discover.groupPackagesLabel" = "Packages in {group}"; +"discover.loadingCategories" = "Loading categories…"; +"discover.noResults" = "Nothing matches \"{query}\"."; +"discover.noResults.body" = "Try a shorter or different term."; +"discover.noResultsInCategories" = "No \"{query}\" results in the selected categories."; +"discover.noResultsInCategories.body" = "Try removing a chip or broadening the search term."; +"discover.packagesInCategory" = "Packages in {category}"; +"discover.removeFilter" = "Remove {category} filter"; +"discover.searchAria" = "Search Homebrew"; +"discover.searchFailed" = "Search failed"; +"discover.searchPlaceholder" = "Search the Homebrew index…"; +"discover.searchPromptTitle" = "Type a query to search the Homebrew index"; +"discover.staleDismissAria" = "Dismiss catalog staleness banner"; +"discover.subgroupNote" = "grouped by overlapping category"; +"discover.subgroupsBody" = "Homebrew has no official sub-category data, so this is a co-occurrence grouping: within \"{category}\", packages are bucketed by the other categories they also belong to. A package with several overlapping categories appears in each bucket. \"General {category}\" holds packages tagged only \"{category}\" - it's often the largest group, and this is a grouping aid, not a strict taxonomy."; +"discover.subgroupsLabel" = "About sub-categories"; +"discover.subgroupsTitle" = "How these sub-groups are built"; +"package.installTrendFor" = "{name} install trend"; +"package.openToken" = "Open {token}"; +"recovery.reason.alreadyInstalled" = "{name} is already installed outside Homebrew."; +"recovery.reason.stillRequired" = "{name} is still required by another installed package."; +"vulnerabilities.scanFailed" = "Vulnerability scan failed"; +"vulnerabilities.scanOneFailed" = "Vulnerability scan failed for {name}"; +"settings.github.corruptSettings" = "Settings file unreadable - visit the Network section to reset."; +"settings.github.githubUserFallback" = "github user"; +"settings.github.privacy.afterStruct" = " view crosses the IPC boundary."; +"settings.github.privacy.beforeStruct" = "brew-browser stores your token in {keyringName}. The token is never sent over IPC to the renderer, never written to disk, and never logged. Only the derived "; +"settings.github.scopes.afterNotifications" = " to enable watching. No private-repo access, no email read, no admin."; +"settings.github.scopes.afterPublicRepo" = " to enable starring and issue filing, and "; +"settings.github.scopes.afterReadUser" = " to display your username, "; +"settings.github.scopes.beforeReadUser" = "Sign-in is optional. The minimum scopes requested are "; +"settings.github.signInHint" = "Opens GitHub's standard \"Authorize app\" flow in your browser. No password is ever entered into brew-browser. This is required to Star, Watch, and file issues on GitHub with brew-browser."; +"settings.github.signInPurpose" = "What sign-in is used for"; +"settings.github.signedInAs" = "Signed in as"; +"settings.github.statsHint.afterHost" = "."; +"settings.github.statsHint.beforeHost" = "Show repo stars, forks, and last release for any package whose homepage is a GitHub URL. brew-browser fetches public repo metadata from "; +"settings.github.scopesLabel" = "Scopes"; diff --git a/native/Sources/BrewBrowserKit/Resources/enrichment.ru.json b/native/Sources/BrewBrowserKit/Resources/enrichment.ru.json new file mode 100644 index 0000000..84133df --- /dev/null +++ b/native/Sources/BrewBrowserKit/Resources/enrichment.ru.json @@ -0,0 +1,314 @@ +{ + "base_version": "2026-05-24T11:53:38Z", + "entries": { + "curl": { + "friendly_name": "curl", + "search_terms": [ + "http клиент", + "api", + "запрос", + "скачать", + "сеть", + "диагностика" + ], + "summary": "Консольный клиент для передачи данных по URL: HTTP, HTTPS, FTP и другим протоколам. Полезен для проверки API, загрузок и сетевой диагностики.", + "use_cases": [ + "Проверять HTTP API из терминала", + "Скачивать файлы и ответы сервисов", + "Отлаживать сетевые запросы" + ] + }, + "docker": { + "friendly_name": "Docker", + "search_terms": [ + "контейнеры", + "docker", + "образ", + "devops", + "локальная разработка", + "окружение" + ], + "summary": "Платформа контейнеризации для сборки, запуска и распространения приложений в контейнерах. Установите для локальной разработки и воспроизводимых окружений.", + "use_cases": [ + "Запускать сервисы в контейнерах", + "Собирать образы приложений", + "Повторять локально окружения, близкие к боевым" + ] + }, + "ffmpeg": { + "friendly_name": "FFmpeg", + "search_terms": [ + "видео", + "аудио", + "конвертация", + "транскодирование", + "медиа", + "ffmpeg" + ], + "summary": "Набор инструментов для записи, конвертации, обработки и потоковой передачи аудио и видео. Установите для CLI-обработки медиафайлов и автоматизации транскодирования.", + "use_cases": [ + "Конвертировать видео и аудио", + "Обрезать и перекодировать медиафайлы", + "Автоматизировать обработку роликов" + ] + }, + "firefox": { + "friendly_name": "Firefox", + "search_terms": [ + "браузер", + "mozilla", + "веб", + "приватность", + "devtools", + "сайты" + ], + "summary": "Веб-браузер Mozilla с акцентом на приватность, расширения и инструменты разработчика. Установите для повседневного просмотра и тестирования сайтов.", + "use_cases": [ + "Просматривать сайты", + "Тестировать веб-интерфейсы", + "Использовать инструменты разработчика" + ] + }, + "git": { + "friendly_name": "Git", + "search_terms": [ + "контроль версий", + "репозиторий", + "коммит", + "ветка", + "github", + "исходный код" + ], + "summary": "Распределённая система контроля версий для исходного кода и текстовых проектов. Установите для работы с репозиториями, ветками, коммитами и совместной разработкой.", + "use_cases": [ + "Клонировать и вести репозитории", + "Работать с ветками и коммитами", + "Синхронизировать код с GitHub или другим хостингом" + ] + }, + "google-chrome": { + "friendly_name": "Google Chrome", + "search_terms": [ + "браузер", + "chrome", + "google", + "веб", + "devtools", + "тестирование сайтов" + ], + "summary": "Веб-браузер Google с широкой совместимостью, синхронизацией и Chrome DevTools. Установите для веб-разработки, тестирования и ежедневного браузинга.", + "use_cases": [ + "Тестировать сайты в Chrome", + "Использовать Chrome DevTools", + "Синхронизировать профиль Google" + ] + }, + "kubernetes-cli": { + "friendly_name": "kubectl", + "search_terms": [ + "kubernetes", + "kubectl", + "кластер", + "контейнеры", + "devops", + "манифесты" + ], + "summary": "CLI kubectl для управления кластерами Kubernetes. Установите, чтобы просматривать ресурсы, применять манифесты и отлаживать рабочие нагрузки из терминала.", + "use_cases": [ + "Управлять Kubernetes-кластерами", + "Применять YAML-манифесты", + "Отлаживать Pods, Services и Deployments" + ] + }, + "neovim": { + "friendly_name": "Neovim", + "search_terms": [ + "редактор", + "vim", + "терминал", + "код", + "lua", + "lsp" + ], + "summary": "Современный форк Vim с Lua-конфигурацией, LSP-интеграцией и расширяемым рабочим процессом, ориентированным на терминал. Установите для мощного редактирования кода в терминале.", + "use_cases": [ + "Редактировать код в терминале", + "Настроить LSP и плагины", + "Использовать Vim-совместимый рабочий процесс" + ] + }, + "node": { + "friendly_name": "Node.js", + "search_terms": [ + "javascript", + "nodejs", + "npm", + "фронтенд", + "бэкенд", + "сборка" + ], + "summary": "Среда выполнения JavaScript вне браузера. Установите для бэкенд-сервисов, CLI-инструментов, сборки фронтенд-проектов и npm-экосистемы.", + "use_cases": [ + "Запускать JavaScript-сервисы и CLI", + "Использовать npm и npm-скрипты", + "Собирать фронтенд-проекты" + ] + }, + "openssl@3": { + "friendly_name": "OpenSSL 3", + "search_terms": [ + "tls", + "ssl", + "криптография", + "сертификаты", + "ключи", + "openssl" + ], + "summary": "Версия OpenSSL 3 для TLS, криптографии, сертификатов и связанных библиотек. Установите как зависимость или для работы с ключами и сертификатами из терминала.", + "use_cases": [ + "Проверять TLS-сертификаты", + "Работать с ключами и CSR", + "Собирать проекты, которым нужен OpenSSL 3" + ] + }, + "postgresql@14": { + "friendly_name": "PostgreSQL 14", + "search_terms": [ + "база данных", + "sql", + "postgres", + "postgresql", + "реляционная база", + "сервер" + ], + "summary": "Версия PostgreSQL 14, объектно-реляционной базы данных. Установите, если приложение или окружение разработки требует именно эту стабильную ветку.", + "use_cases": [ + "Запускать локальную SQL-базу", + "Разрабатывать приложения с PostgreSQL 14", + "Тестировать миграции для конкретной версии" + ] + }, + "python@3.13": { + "friendly_name": "Python 3.13", + "search_terms": [ + "python", + "питон", + "интерпретатор", + "скрипты", + "автоматизация", + "анализ данных" + ], + "summary": "Версия интерпретатора Python 3.13 для разработки, автоматизации и анализа данных. Установите, если проекту нужна именно эта ветка Python.", + "use_cases": [ + "Запускать проекты на Python 3.13", + "Создавать виртуальные окружения", + "Автоматизировать задачи и обработку данных" + ] + }, + "redis": { + "friendly_name": "Redis", + "search_terms": [ + "redis", + "кэш", + "очередь", + "in-memory", + "брокер", + "pubsub" + ], + "summary": "Быстрое in-memory хранилище данных, часто используемое как кэш, очередь или брокер сообщений. Установите для локальной разработки и тестирования Redis-зависимых сервисов.", + "use_cases": [ + "Запускать локальный кэш", + "Тестировать очереди и pub/sub", + "Разрабатывать сервисы с Redis" + ] + }, + "ripgrep": { + "friendly_name": "ripgrep", + "search_terms": [ + "поиск", + "grep", + "регулярные выражения", + "кодовая база", + "файлы", + "терминал" + ], + "summary": "Очень быстрый инструмент поиска по файлам и каталогам с поддержкой регулярных выражений. Установите для поиска по исходному коду и большим деревьям файлов.", + "use_cases": [ + "Искать текст в кодовой базе", + "Фильтровать файлы регулярными выражениями", + "Заменить медленный grep в ежедневной работе" + ] + }, + "slack": { + "friendly_name": "Slack", + "search_terms": [ + "чат", + "коммуникации", + "сообщения", + "команда", + "уведомления", + "slack" + ], + "summary": "Десктопный клиент Slack для командных чатов, каналов и уведомлений. Установите, если команда использует Slack для рабочей коммуникации.", + "use_cases": [ + "Общаться в рабочих каналах", + "Получать уведомления команды", + "Искать сообщения и файлы в Slack" + ] + }, + "visual-studio-code": { + "friendly_name": "Visual Studio Code", + "search_terms": [ + "редактор кода", + "ide", + "vscode", + "разработка", + "расширения", + "microsoft" + ], + "summary": "Редактор кода от Microsoft с расширениями, встроенным терминалом и поддержкой множества языков. Установите как универсальный редактор для повседневной разработки.", + "use_cases": [ + "Редактировать код с расширениями", + "Работать во встроенном терминале", + "Настроить редактор для разных языков" + ] + }, + "vlc": { + "friendly_name": "VLC media player", + "search_terms": [ + "видео", + "аудио", + "медиаплеер", + "плеер", + "кодеки", + "vlc" + ], + "summary": "Медиаплеер с поддержкой множества аудио- и видеоформатов без дополнительных кодеков. Установите для воспроизведения локальных файлов и потокового видео.", + "use_cases": [ + "Воспроизводить видео и аудио", + "Открывать редкие медиаформаты", + "Смотреть сетевые потоки" + ] + }, + "wget": { + "friendly_name": "Wget", + "search_terms": [ + "скачать", + "загрузка файлов", + "http", + "ftp", + "зеркалирование", + "консольная загрузка" + ], + "summary": "Консольная утилита для загрузки файлов по HTTP, HTTPS и FTP. Установите, если нужно скачивать файлы, зеркалировать сайты или автоматизировать загрузки в скриптах.", + "use_cases": [ + "Скачивать файлы из shell-скриптов", + "Зеркалировать сайты и каталоги", + "Автоматизировать пакетные загрузки" + ] + } + }, + "generated_at": "2026-07-08T00:00:00Z", + "locale": "ru", + "version": "2026-07-08-ru-seed" +} diff --git a/native/Sources/BrewBrowserKit/Resources/ru.lproj/Localizable.strings b/native/Sources/BrewBrowserKit/Resources/ru.lproj/Localizable.strings new file mode 100644 index 0000000..9bee143 --- /dev/null +++ b/native/Sources/BrewBrowserKit/Resources/ru.lproj/Localizable.strings @@ -0,0 +1,830 @@ +"AI & ML" = "ИИ и ML"; +"Browsers" = "Браузеры"; +"Cloud & DevOps" = "Облако и DevOps"; +"Communication" = "Коммуникации"; +"Data" = "Данные"; +"Developer Tools" = "Инструменты разработчика"; +"Editors & IDEs" = "Редакторы и IDE"; +"Education" = "Образование"; +"Games & Entertainment" = "Игры и развлечения"; +"Graphics & Design" = "Графика и дизайн"; +"Music" = "Музыка"; +"Office & Docs" = "Офис и документы"; +"Productivity" = "Продуктивность"; +"System Utilities" = "Системные утилиты"; +"Video & Audio" = "Видео и аудио"; +"Writing" = "Работа с текстом"; +"Uncategorized" = "Без категории"; +"app.controls" = "Элементы управления приложением"; +"chrome.github.connectionStatus" = "Статус подключения GitHub"; +"chrome.donate.aria" = "Поддержать brew-browser через GitHub Sponsors"; +"chrome.donate.title" = "Поддержать через GitHub Sponsors"; +"command.openActivity" = "Открыть активность"; +"command.openDiscover" = "Открыть каталог"; +"command.openLibrary" = "Открыть библиотеку"; +"command.openSnapshots" = "Открыть снимки"; +"command.openTrending" = "Открыть тренды"; +"command.refreshLibrary" = "Обновить библиотеку"; +"command.refreshSnapshots" = "Обновить снимки"; +"command.refreshTrending" = "Обновить тренды"; +"command.toggleActivityDrawer" = "Показать или скрыть журнал активности"; +"github.connectedAs" = "GitHub: выполнен вход как @{username}"; +"github.scopeIncomplete" = "GitHub: вход выполнен, но не хватает разрешений — нажмите, чтобы исправить"; +"keyboard.close" = "закрыть"; +"keyboard.navigate" = "перейти"; +"keyboard.open" = "открыть"; +"language.english" = "Английский"; +"language.hint.reload" = "После смены языка brew-browser перезагрузится."; +"language.label" = "Язык"; +"language.russian" = "Русский"; +"language.system" = "Язык системы"; +"nav.activity" = "Активность"; +"nav.dashboard" = "Сводка"; +"nav.discover" = "Каталог"; +"nav.library" = "Библиотека"; +"nav.primary" = "Основная навигация"; +"nav.services" = "Службы"; +"nav.snapshots" = "Снимки"; +"nav.trending" = "Тренды"; +"nav.showSidebar" = "Показать боковую панель"; +"nav.hideSidebar" = "Скрыть боковую панель"; +"package.installed" = "установлен"; +"package.kind.cask" = "cask-пакет"; +"package.kind.formula" = "формула"; +"palette.empty" = "Ничего не найдено."; +"palette.group.actions" = "Действия"; +"palette.group.commands" = "Команды"; +"palette.group.index" = "Каталог"; +"palette.group.installed" = "Установленные"; +"palette.results" = "Результаты палитры команд"; +"palette.search.placeholder" = "Введите команду, пакет или раздел."; +"palette.searchLabel" = "Поиск в палитре команд"; +"palette.title" = "Палитра команд"; +"palette.group.view" = "Вид"; +"search.allResultsInDiscover" = "Все результаты в Каталоге →"; +"search.clear" = "Очистить"; +"search.clearSearch" = "Очистить поиск"; +"search.noMatches" = "По запросу «{query}» ничего не найдено."; +"search.packages" = "Поиск пакетов"; +"search.packagesPlaceholder" = "Поиск пакетов…"; +"search.results" = "Результаты поиска"; +"search.searching" = "Идёт поиск…"; +"settings.open" = "Открыть настройки"; +"settings.close" = "Закрыть настройки"; +"settings.paneLabel" = "Настройки — {section}"; +"settings.sections" = "Разделы настроек"; +"settings.section.about" = "О приложении"; +"settings.section.activity" = "Активность"; +"settings.section.appearance" = "Оформление"; +"settings.section.brew" = "Brew"; +"settings.section.github" = "GitHub"; +"settings.section.network" = "Сеть"; +"settings.shortcutTitle" = "Настройки (⌘,)"; +"settings.title" = "Настройки"; +"theme.change" = "Изменить тему"; +"theme.dark" = "Тёмная"; +"theme.light" = "Светлая"; +"theme.title" = "Тема: {theme}"; +"theme.system" = "Системная"; +"trending.window.30d" = "30 дн."; +"trending.window.90d" = "90 дн."; +"trending.window.365d" = "365 дн."; +"updates.available" = "Доступно обновление"; +"updates.availableLabel" = "Доступно обновление: brew-browser {version}. Нажмите, чтобы открыть Настройки."; +"updates.dismissVersion" = "Скрыть уведомление об обновлении до v{version}"; +"updates.installing" = "Устанавливаем обновление"; +"updates.skipVersion" = "Пропустить эту версию"; +"updates.versionAvailable" = "Доступна версия brew-browser {version}"; +"vulnerability.packages" = "Уязвимых пакетов: {count}"; +"About" = "О приложении"; +"About Brew Browser" = "О Brew Browser"; +"Activity" = "Активность"; +"Actions" = "Действия"; +"Advanced" = "Дополнительно"; +"AI features" = "Функции ИИ"; +"All" = "Все"; +"All Categories" = "Все категории"; +"All current" = "Всё актуально"; +"Analytics" = "Аналитика"; +"Appearance" = "Оформление"; +"App version" = "Версия приложения"; +"Autoremove" = "Удалить лишние зависимости"; +"Autoremove unused dependencies" = "Удалить неиспользуемые зависимости"; +"Auto-check daily" = "Проверять ежедневно"; +"Body (Markdown)" = "Текст (Markdown)"; +"Brew" = "Brew"; +"Browse" = "Обзор"; +"Cancel" = "Отмена"; +"Casks" = "Cask-пакеты"; +"Casks (Caskroom)" = "Cask-пакеты (Caskroom)"; +"Caveats" = "Примечания"; +"Check for updates now" = "Проверить обновления сейчас"; +"Check now" = "Проверить"; +"Choose packages to upgrade" = "Выберите пакеты для обновления"; +"Choose…" = "Выбрать…"; +"Categories unavailable" = "Категории недоступны"; +"Categories" = "Категории"; +"Clear" = "Очистить"; +"Clear completed" = "Очистить завершённые"; +"Clear Finished" = "Очистить завершённые"; +"Clear categories" = "Сбросить категории"; +"Clear category filter" = "Сбросить фильтр категорий"; +"Clear filter" = "Сбросить фильтр"; +"Click the code to copy it." = "Нажмите код, чтобы скопировать его."; +"Close" = "Закрыть"; +"Command Palette…" = "Палитра команд…"; +"Composition" = "Состав"; +"Confirm before uninstall / zap" = "Подтверждать удаление и полную очистку (zap)"; +"Confirmations" = "Подтверждения"; +"Contacting GitHub…" = "Связываемся с GitHub…"; +"Create" = "Создать"; +"Dashboard" = "Сводка"; +"Dark" = "Тёмная"; +"Default landing" = "Стартовый раздел"; +"Delete" = "Удалить"; +"Dependency" = "Зависимость"; +"Description" = "Описание"; +"Download cache" = "Кэш загрузок"; +"Deselect all" = "Снять выбор"; +"Discover" = "Каталог"; +"Dismiss" = "Скрыть"; +"Dismiss for this session" = "Скрыть до конца сеанса"; +"Donate to the project" = "Поддержать проект"; +"Enter this code:" = "Введите этот код:"; +"Esc" = "Esc"; +"Export…" = "Экспорт…"; +"Exposure" = "Уязвимости"; +"Fetch latest categories & descriptions" = "Обновлять категории и описания"; +"Fetch trending history" = "Загружать историю трендов"; +"File issue" = "Сообщить о проблеме"; +"File an issue" = "Сообщить о проблеме"; +"Filter" = "Фильтр"; +"Formulae" = "Формулы"; +"Formulae (Cellar)" = "Формулы (Cellar)"; +"GitHub" = "GitHub"; +"Greedy upgrades (include self-updating casks)" = "Обновлять также самообновляемые cask-пакеты"; +"Homebrew" = "Homebrew"; +"Icon source" = "Источник иконки"; +"Import…" = "Импорт…"; +"Install" = "Установить"; +"Install brew-vulns" = "Установить brew-vulns"; +"Install complete. Relaunch to use the new version." = "Установка завершена. Перезапустите приложение, чтобы перейти на новую версию."; +"Install trend" = "Динамика установок"; +"Installed" = "Установлено"; +"Installed only" = "Только установленные"; +"Installing…" = "Устанавливаем…"; +"instead" = "вместо"; +"Keep last N completed jobs" = "Хранить последние N завершённых задач"; +"Language" = "Язык"; +"Labels" = "Метки"; +"Latest" = "Актуальная версия"; +"Library" = "Библиотека"; +"License" = "Лицензия"; +"License mismatch" = "Лицензии не совпадают"; +"Light" = "Светлая"; +"Lines per job" = "Строк на задачу"; +"Loading GitHub stats…" = "Загружаем статистику GitHub…"; +"Loading settings…" = "Загружаем настройки…"; +"Loading sign-in status…" = "Проверяем вход…"; +"Logs (var/log)" = "Логи (var/log)"; +"Manual" = "Вручную"; +"Name" = "Имя"; +"Network" = "Сеть"; +"New Snapshot" = "Новый снимок"; +"No beta channel in this release." = "В этом релизе нет бета-канала."; +"No background services" = "Фоновых служб нет"; +"No known vulnerabilities." = "Известных уязвимостей нет."; +"No known vulnerabilities" = "Известных уязвимостей нет"; +"No outdated packages." = "Устаревших пакетов нет."; +"No results." = "Ничего не найдено."; +"No snapshots yet" = "Снимков пока нет"; +"Not enough data yet" = "Пока недостаточно данных"; +"Not scanned yet." = "Проверка ещё не выполнялась."; +"Offline Mode" = "Офлайн-режим"; +"Off" = "Выкл."; +"OK" = "OK"; +"Open in browser" = "Открыть в браузере"; +"Open in Finder" = "Показать в Finder"; +"Outbound paths (read-only)" = "Исходящие сетевые обращения (только чтение)"; +"Package detail" = "Карточка пакета"; +"Patched in" = "Исправлено в"; +"Pinned" = "Закреплён"; +"Pinned packages are not upgraded by brew. Unpin from the package detail page." = "Закреплённые пакеты не обновляются через brew. Открепите пакет в его карточке."; +"Recent" = "Недавние"; +"RECENT" = "НЕДАВНИЕ"; +"Refresh" = "Обновить"; +"Refresh from brew.sh →" = "Обновить с brew.sh →"; +"Refreshing…" = "Обновляем…"; +"Remove" = "Убрать"; +"Repository" = "Репозиторий"; +"Reset to defaults" = "Сбросить настройки"; +"Resize package detail panel" = "Изменить ширину карточки пакета"; +"Restore" = "Восстановить"; +"Retry" = "Повторить"; +"Re-scan all" = "Проверить всё заново"; +"Scan all packages" = "Проверить все пакеты"; +"Scan installed packages for known vulnerabilities" = "Проверять установленные пакеты на известные уязвимости"; +"Scan now" = "Проверить сейчас"; +"Scanning all…" = "Проверяем все пакеты…"; +"Scanning…" = "Проверяем…"; +"Scrub — also remove the latest versions' cached downloads (more aggressive)" = "Тщательная очистка: удалить также кэш загрузок последних версий (агрессивнее)"; +"Search" = "Поиск"; +"Search packages…" = "Поиск пакетов…"; +"Searching…" = "Идёт поиск…"; +"Security" = "Безопасность"; +"Select all" = "Выбрать все"; +"Send Homebrew install analytics" = "Отправлять аналитику установок Homebrew"; +"Service" = "Служба"; +"Services" = "Службы"; +"Settings" = "Настройки"; +"Settings file is unreadable." = "Файл настроек не читается."; +"Show GitHub stats on package pages" = "Показывать статистику GitHub на страницах пакетов"; +"Show sidebar" = "Показать боковую панель"; +"Hide sidebar" = "Скрыть боковую панель"; +"Sign in" = "Вход"; +"Sign in to GitHub" = "Войти в GitHub"; +"Sign in with GitHub" = "Войти через GitHub"; +"Sign out" = "Выйти"; +"Sign-in denied." = "Вход отклонён."; +"Similar packages" = "Похожие пакеты"; +"Size" = "Размер"; +"Snapshot" = "Снимок"; +"Snapshots" = "Снимки"; +"Stable" = "Стабильный"; +"Start" = "Запустить"; +"Stop" = "Остановить"; +"Storage" = "Хранилище"; +"Status" = "Статус"; +"Submit" = "Отправить"; +"Support brew-browser via GitHub Sponsors" = "Поддержать brew-browser через GitHub Sponsors"; +"System" = "Системная"; +"System language" = "Язык системы"; +"Tags" = "Теги"; +"Tap" = "Tap"; +"Terminal" = "Терминал"; +"Theme" = "Тема"; +"Title" = "Заголовок"; +"Token" = "Токен"; +"Top categories in your library" = "Главные категории в библиотеке"; +"Trending" = "Тренды"; +"Trending cache TTL" = "TTL кэша трендов"; +"Try again" = "Попробовать снова"; +"Type" = "Тип"; +"Type a command, package, or section." = "Введите команду, пакет или раздел."; +"Update available" = "Доступно обновление"; +"Update channel" = "Канал обновлений"; +"Updates" = "Обновления"; +"Updates available" = "Доступны обновления"; +"Vulnerable" = "Уязвимые"; +"Velocity" = "Динамика"; +"Upgrade" = "Обновить"; +"Upgrade all" = "Обновить всё"; +"Upgrade available" = "Доступно обновление"; +"Upgrade to fix" = "Обновить, чтобы исправить"; +"Version" = "Версия"; +"Verbose — list every file removed" = "Подробный вывод: перечислить все удаляемые файлы"; +"View vulnerable packages →" = "Показать уязвимые пакеты →"; +"Waiting for authorization…" = "Ожидаем авторизации…"; +"Watch" = "Отслеживать"; +"Watching" = "Отслеживается"; +"Why install this?" = "Зачем это устанавливать?"; +"Window" = "Окно"; +"Window vibrancy" = "Эффект прозрачности окна"; +"— all enrichment is baked into the app binary." = "— всё уже включено в приложение."; +"Zero LLM calls are made from your machine" = "С вашего компьютера не выполняются LLM-запросы"; +"Zero telemetry. Zero accounts." = "Никакой телеметрии. Никаких учётных записей."; +"built with" = "создано с помощью"; +"close" = "закрыть"; +"close Esc" = "Закрыть (Esc)"; +"dependency" = "зависимость"; +"formula" = "формула"; +"cask" = "cask"; +"installed" = "установлено"; +"not present" = "нет на диске"; +"pinned" = "закреплён"; +"source" = "источник"; +"unavailable" = "недоступно"; +"working…" = "выполняется…"; +"Adopt existing" = "Использовать существующую копию"; +"Backend not available" = "Бэкенд недоступен"; +"Catalog refresh failed" = "Не удалось обновить каталог"; +"Change theme" = "Изменить тему"; +"Changing the language reloads brew-browser." = "После смены языка brew-browser перезагрузится."; +"Cleanup failed" = "Не удалось очистить кэш"; +"Clean up cache…" = "Очистить кэш…"; +"Clean up" = "Очистить"; +"Cleaning…" = "Очищаем…"; +"Cleaning up Homebrew cache" = "Очищаем кэш Homebrew"; +"Command palette" = "Палитра команд"; +"Command palette search" = "Поиск в палитре команд"; +"Command palette results" = "Результаты палитры команд"; +"Couldn't change brew analytics" = "Не удалось изменить аналитику brew"; +"Couldn't load services" = "Не удалось загрузить службы"; +"Couldn't load snapshots" = "Не удалось загрузить снимки"; +"Couldn't load packages." = "Не удалось загрузить пакеты."; +"Couldn't open in file manager" = "Не удалось открыть в файловом менеджере"; +"Couldn't reveal in Finder" = "Не удалось показать в Finder"; +"Disk probe failed" = "Не удалось проверить диск"; +"Donate via GitHub Sponsors" = "Поддержать через GitHub Sponsors"; +"Donate to brew-browser on GitHub Sponsors" = "Поддержать brew-browser через GitHub Sponsors"; +"Everything is up to date" = "Всё актуально"; +"English" = "Английский"; +"Follows the macOS theme when set to System." = "Следует теме macOS, если выбрана системная тема."; +"Failed to load packages" = "Не удалось загрузить пакеты"; +"Force remove" = "Удалить принудительно"; +"GitHub connection status" = "Статус подключения GitHub"; +"Homebrew analytics disabled" = "Аналитика Homebrew отключена"; +"Homebrew analytics enabled" = "Аналитика Homebrew включена"; +"Homebrew was not found on this Mac." = "Homebrew не найден на этом Mac."; +"Index" = "Индекс"; +"Installed as a dependency, not requested directly" = "Установлено как зависимость, а не вручную"; +"Invalid URL" = "Недопустимый URL"; +"Loading your packages…" = "Загружаем пакеты…"; +"Measuring disk usage…" = "Считаем место на диске…"; +"Nothing's run yet." = "Пока ничего не запускалось."; +"Open Activity" = "Открыть активность"; +"Open Discover" = "Открыть каталог"; +"Open Library" = "Открыть библиотеку"; +"Open Settings" = "Открыть настройки"; +"Open Snapshots" = "Открыть снимки"; +"Open Trending" = "Открыть тренды"; +"Open Library, filter outdated" = "Открыть библиотеку с фильтром устаревших"; +"Other" = "Другое"; +"Pick which packages to upgrade" = "Выбрать пакеты для обновления"; +"Refresh Library" = "Обновить библиотеку"; +"Refresh Snapshots" = "Обновить снимки"; +"Refresh Trending" = "Обновить тренды"; +"Refresh disk usage" = "Обновить сведения о диске"; +"Refreshed" = "Обновлено"; +"Report an issue on GitHub" = "Сообщить о проблеме на GitHub"; +"Report to brew-browser" = "Отправить отчёт в brew-browser"; +"Remove from history" = "Убрать из истории"; +"Remove it anyway, ignoring packages that depend on it." = "Удалить принудительно, игнорируя пакеты, которые от него зависят."; +"Remove unused dependencies?" = "Удалить неиспользуемые зависимости?"; +"Removing unused dependencies" = "Удаляем неиспользуемые зависимости"; +"Removing…" = "Удаляем…"; +"Replace the existing copy with a fresh Homebrew install." = "Заменить существующую копию новой установкой из Homebrew."; +"Run brew doctor" = "Запустить brew doctor"; +"Run brew doctor and stream the diagnostics into Activity" = "Запустить brew doctor и вывести диагностику в панель активности"; +"Run brew update, refresh the catalog from formulae.brew.sh, and reload your installed list" = "Запустить brew update, обновить каталог с formulae.brew.sh и перечитать список установленных пакетов"; +"Russian" = "Русский"; +"Search packages" = "Поиск пакетов"; +"Search results" = "Результаты поиска"; +"See the Activity drawer." = "Подробности в панели Активность."; +"Settings (⌘,)" = "Настройки (⌘,)"; +"Settings sections" = "Разделы настроек"; +"Submit issue" = "Отправить проблему"; +"This will install packages from the snapshot. Existing packages are skipped." = "Будут установлены пакеты из снимка. Уже установленные пакеты пропускаются."; +"Toggle Activity drawer" = "Показать или скрыть журнал активности"; +"Upgrade-all failed" = "Не удалось обновить всё"; +"Upgrading…" = "Обновляем…"; +"When off, only Homebrew's native metadata appears. Categories (sidebar tile grid, donut chart, chip filters) are also hidden because they're AI-generated." = "Если выключено, отображаются только штатные метаданные Homebrew. Категории (плитки в боковой панели, кольцевая диаграмма и фильтры по категориям) тоже скрываются, потому что они сгенерированы ИИ."; +"When on, brew-browser shows extra metadata generated at build time by AI: friendly names, expanded descriptions, use cases, similar package suggestions, and category tags." = "Если включено, brew-browser показывает дополнительные метаданные, сгенерированные ИИ при сборке: понятные названия, расширенные описания, сценарии использования, похожие пакеты и теги категорий."; +"Which section opens when you launch brew-browser." = "Какой раздел открывается при запуске brew-browser."; +"Requires app restart to take effect. The default (HudWindow) matches the rest of macOS." = "Вступит в силу после перезапуска приложения. Значение по умолчанию (HudWindow) соответствует остальной macOS."; +"all packages" = "все пакеты"; +"brew commands kicked off from here will show up in this list." = "Команды brew, запущенные из приложения, появятся в этом списке."; +"disabled" = "отключено"; +"Advisory" = "Бюллетень безопасности"; +"Catalog unavailable" = "Каталог недоступен"; +"Check this package for known vulnerabilities." = "Проверить этот пакет на известные уязвимости."; +"Commands" = "Команды"; +"Couldn't load packages" = "Не удалось загрузить пакеты"; +"Couldn't load trending" = "Не удалось загрузить тренды"; +"Copy" = "Скопировать"; +"Copy the command to the clipboard" = "Скопировать команду в буфер обмена"; +"Deprecated" = "Устарело"; +"Disabled" = "Отключено"; +"Disabled by Offline Mode" = "Отключено офлайн-режимом"; +"Blocked by Offline Mode. Disable in Settings → Network." = "Заблокировано офлайн-режимом. Отключите его в разделе «Настройки → Сеть»."; +"Fetching install counts from formulae.brew.sh…" = "Загружаем счётчики установок с formulae.brew.sh…"; +"Homebrew isn't installed" = "Homebrew не установлен"; +"Install Homebrew" = "Установить Homebrew"; +"Install `brew vulns` to scan this package for known vulnerabilities." = "Установите `brew vulns`, чтобы проверить этот пакет на известные уязвимости."; +"Install the Xcode Command Line Tools" = "Установить Xcode Command Line Tools"; +"Installs" = "Установки"; +"Installs, upgrades, uninstalls, and updates show up here." = "Здесь отображаются установки, обновления пакетов, удаления и обновления Homebrew."; +"Loading snapshots…" = "Загружаем снимки…"; +"Loading the Homebrew catalog…" = "Загружаем каталог Homebrew…"; +"Members also categorized here" = "Пакеты, которые также входят в эту категорию"; +"Members of this category with no other category" = "Пакеты только из этой категории"; +"No activity yet" = "Активности пока нет"; +"No packages" = "Пакетов нет"; +"No results" = "Ничего не найдено"; +"No summary available — view details" = "Нет краткого описания — открыть подробности"; +"Not installed" = "Не установлено"; +"Offline Mode is on — trending needs formulae.brew.sh." = "Офлайн-режим включён — трендам нужен доступ к formulae.brew.sh."; +"Open Dashboard" = "Открыть сводку"; +"Open Services" = "Открыть службы"; +"Open Terminal" = "Открыть Терминал"; +"Outdated" = "Есть обновления"; +"Paste the official install command (from brew.sh) into Terminal:" = "Вставьте официальную команду установки с brew.sh в Терминал:"; +"Reading your Homebrew install…" = "Читаем конфигурацию Homebrew…"; +"Restart" = "Перезапустить"; +"Runs brew vulns across your whole install — it can't scan a single package" = "Запускает brew vulns для всей установки Homebrew; отдельный пакет проверить нельзя"; +"Save your current setup so you can restore it on another Mac. Snapshots live in ~/Library/Application Support/brew-browser/brewfiles/ — findable outside the app too." = "Сохраните текущую конфигурацию, чтобы восстановить её на другом Mac. Снимки лежат в ~/Library/Application Support/brew-browser/brewfiles/ — их можно найти и вне приложения."; +"Scanning all packages…" = "Проверяем все пакеты…"; +"Sign in to GitHub happens on first action." = "Вход в GitHub откроется при первом действии."; +"Sign in to GitHub to star this repository" = "Войдите в GitHub, чтобы поставить звезду репозиторию"; +"Sign in to GitHub to watch this repository" = "Войдите в GitHub, чтобы отслеживать репозиторий"; +"Snapshot error" = "Ошибка снимка"; +"Star" = "Поставить звезду"; +"Star this repository" = "Поставить звезду репозиторию"; +"Starred" = "Звезда поставлена"; +"Stop watching" = "Не отслеживать"; +"Stored in ~/Library/Application Support/brew-browser/brewfiles/" = "Хранится в ~/Library/Application Support/brew-browser/brewfiles/"; +"The Brewfile will be removed from disk. This cannot be undone." = "Brewfile будет удалён с диска. Это действие нельзя отменить."; +"The Homebrew installer needs them. Run this in Terminal and follow the prompt:" = "Они нужны установщику Homebrew. Выполните это в Терминале и следуйте подсказкам:"; +"The bundled package catalog couldn't be loaded." = "Не удалось загрузить встроенный каталог пакетов."; +"The brew-vulns subcommand isn't installed." = "Подкоманда brew-vulns не установлена."; +"settings.vulnerabilities.cleanResult" = "В установленных пакетах известные уязвимости не найдены."; +"settings.vulnerabilities.helperInstalledTitle" = "brew-vulns установлен"; +"settings.vulnerabilities.initialScanBody" = "Запускаем первичную проверку…"; +"settings.vulnerabilities.installFailedTitle" = "Не удалось установить brew-vulns"; +"settings.vulnerabilities.installPrompt" = "Для проверки уязвимостей нужна официальная подкоманда brew vulns. Установить её сейчас? Будет выполнено brew install homebrew/brew-vulns/brew-vulns."; +"settings.vulnerabilities.lastScan" = "Последняя проверка"; +"settings.vulnerabilities.offlineHint" = "Офлайн-режим включён — проверка уязвимостей заблокирована, даже если переключатель включён. Отключите офлайн-режим выше, чтобы разрешить проверку."; +"settings.vulnerabilities.privacyHint" = "Опционально, выключено по умолчанию. При включении brew-browser запускает официальную подкоманду brew vulns проекта Homebrew: она запрашивает OSV.dev (сервис Google) об известных уязвимостях в установленных формулах. Если вы также вошли в GitHub, отдельные GHSA-ID дополняются сведениями из api.github.com. Результаты кэшируются локально; список пакетов не покидает ваш компьютер, кроме запросов, которые сама brew vulns отправляет в OSV."; +"settings.vulnerabilities.scanAllTitle" = "Запустить brew vulns для всех установленных формул"; +"This installs packages from the snapshot. Existing packages are skipped." = "Будут установлены пакеты из снимка. Уже установленные пакеты пропускаются."; +"Use" = "Используйте"; +"User" = "Пользователь"; +"Uninstall" = "Удалить"; +"Unstar this repository" = "Убрать звезду с репозитория"; +"Vulnerability Scanning" = "Проверка уязвимостей"; +"Waiting for Homebrew… the app continues automatically once it's installed." = "Ожидаем Homebrew… приложение продолжит работу автоматически после установки."; +"Watch for activity" = "Отслеживать активность"; +"Working… (see Activity)" = "Выполняется… см. «Активность»"; +"Your browser opened to GitHub's device page. Enter this code (it's copied to your clipboard):" = "Браузер открыл страницу GitHub для ввода кода устройства. Введите этот код (он уже скопирован в буфер обмена):"; +"brew-browser hit an unexpected error running this command." = "brew-browser столкнулся с неожиданной ошибкой при запуске этой команды."; +"brew-browser is a window onto Homebrew — install it and the app takes it from there." = "brew-browser работает поверх Homebrew: установите Homebrew, а дальше приложение всё подхватит."; +"formulae.brew.sh returned no entries." = "formulae.brew.sh не вернул записей."; +"instead." = "вместо него."; +"Already running" = "Уже запущено"; +"App Store Version Detected" = "Обнаружена версия из App Store"; +"Cancel sign in" = "Отменить вход"; +"Click to copy" = "Нажмите, чтобы скопировать"; +"Code copied to clipboard" = "Код скопирован в буфер обмена"; +"Code expired. Please try again." = "Срок действия кода истёк. Попробуйте ещё раз."; +"Couldn't copy code" = "Не удалось скопировать код"; +"Delete failed" = "Не удалось удалить"; +"Deleted snapshot" = "Снимок удалён"; +"Export failed" = "Не удалось экспортировать"; +"Exported snapshot" = "Снимок экспортирован"; +"Import failed" = "Не удалось импортировать"; +"Imported snapshot" = "Снимок импортирован"; +"Install & Override" = "Установить с заменой"; +"Installation Blocked" = "Установка заблокирована"; +"Loading brew services…" = "Загружаем службы brew…"; +"No background services." = "Фоновых служб нет."; +"Install something like postgresql, redis, or nginx and they'll show up here." = "Установите, например, postgresql, redis или nginx — службы появятся здесь."; +"Refresh services" = "Обновить службы"; +"Restore complete" = "Восстановление завершено"; +"Restore failed" = "Не удалось восстановить"; +"Snapshot failed" = "Не удалось сохранить снимок"; +"Snapshot saved" = "Снимок сохранён"; +"Start service" = "Запустить службу"; +"Stop service" = "Остановить службу"; +"Restart service" = "Перезапустить службу"; +"Not running" = "Не запущено"; +"Brew services" = "Службы brew"; +"Open package details" = "Открыть карточку пакета"; +"Save your current setup so you can restore it on another machine. Snapshots live in ~/.local/share/brew-browser/brewfiles/ — findable outside the app too." = "Сохраните текущую конфигурацию, чтобы восстановить её на другой машине. Снимки лежат в ~/.local/share/brew-browser/brewfiles/ — их можно найти и вне приложения."; +"This will remove the package from your system." = "Пакет будет удалён из системы."; +"Overwrite manual installation?" = "Заменить ручную установку?"; +"An existing version was found in your Applications folder. If you proceed, Homebrew will force-install and overwrite the existing bundle." = "В папке /Applications уже есть эта версия. Если продолжить, Homebrew принудительно установит пакет и заменит существующее приложение."; +"An existing App Store version was found in your Applications folder." = "В папке /Applications уже есть версия, установленная через App Store."; +"App Store bundles are system-protected and cannot be overwritten by Homebrew." = "Приложения из App Store защищены системой, и Homebrew не сможет заменить их напрямую."; +"Resolution: delete the App Store version from /Applications, then install it again via Homebrew." = "Решение: удалите из /Applications версию, установленную через App Store, затем установите приложение через Homebrew."; +"Use the Sign in with GitHub button in Settings." = "Используйте кнопку «Войти через GitHub» в настройках."; +"Re-authorize" = "Обновить разрешения"; +"Upgrade failed" = "Не удалось обновить"; +"Upgraded packages" = "Пакеты обновлены"; +"Watching package" = "Отслеживаем пакет"; +"Stopped watching package" = "Отслеживание пакета отключено"; +"Starred package" = "Звезда поставлена"; +"Unstarred package" = "Звезда убрана"; +"Signed in as GitHub user." = "Выполнен вход в GitHub."; +"settings.activity.rangeDefault" = "Диапазон {min}–{max}. По умолчанию {default}."; +"settings.activity.retentionNote" = "Новые ограничения применяются только к задачам, которые будут сохранены после изменения настройки. Уже сохранённая история не сокращается задним числом."; +"settings.brew.analyticsDesc.beforeCommand" = "Homebrew по умолчанию отправляет анонимную аналитику установок на formulae.brew.sh. Этот переключатель меняет настройку Homebrew (то же, что выполнить "; +"settings.brew.analyticsDesc.afterCommand" = " в Терминале)."; +"settings.brew.analyticsToggle" = "Отправлять аналитику установок Homebrew"; +"settings.brew.confirmationsDesc" = "Перед необратимыми действиями (удаление пакета, zap-очистка, удаление Brewfile) приложение запрашивает подтверждение. Опытные пользователи могут отключить это, когда будут уверены."; +"settings.brew.advancedDesc.beforeCommand" = "Дополнительные параметры "; +"settings.brew.advancedDesc.afterCommand" = " для опытных пользователей."; +"settings.brew.greedyDesc.beforeFlag" = "Добавляет "; +"settings.brew.greedyDesc.between" = " к "; +"settings.brew.greedyDesc.afterCommand" = ", чтобы обновлялись и cask-пакеты с собственным механизмом обновления (например, Chrome). По умолчанию выключено: режим --greedy может лишний раз переустанавливать такие приложения."; +"settings.brew.autoremoveDesc.beforeCommand" = "Запускает "; +"settings.brew.autoremoveDesc.afterCommand" = ", чтобы удалить формулы, установленные только как зависимости и больше не нужные ни одному пакету."; +"settings.brew.autoremoveConfirm.beforeCommand" = "Будет выполнено "; +"settings.brew.autoremoveConfirm.afterCommand" = ": команда удалит формулы, установленные только как зависимости и больше не используемые другими пакетами."; +"settings.liveEnrichment.title" = "Свежие категории и описания"; +"settings.liveEnrichment.hint.beforeHost" = "brew-browser поставляется со встроенными ИИ-категориями и описаниями. Если включено, приложение обновляет их с "; +"settings.liveEnrichment.hint.afterHost" = ": при обновлении проверяет версию, загружает полный список категорий, если он новее, и получает описание пакета, когда вы открываете его карточку. Отправляется только имя просматриваемого пакета (один HTTP GET); сервер не логирует IP и не использует файлы cookie. Это собственный эндпоинт проекта brew-browser, отдельная зона доверия от постоянно доступных путей Homebrew. Требует включить функции ИИ."; +"settings.liveEnrichment.offline" = "Офлайн-режим включён — переключатель заблокирован. Отключите офлайн-режим выше, чтобы разрешить обновление категорий и описаний."; +"settings.trendingHistory.title" = "Расширенная история трендов"; +"settings.trendingHistory.hint.beforeHost" = "Если включено, brew-browser загружает историческую динамику установок по пакетам с "; +"settings.trendingHistory.hint.afterHost" = " для мини-графиков в строках вкладки «Тренды» и графика в карточке пакета. Отправляется только имя просматриваемого пакета (один HTTP GET на пакет); сервер не логирует IP, не использует файлы cookie и не собирает цифровой отпечаток. Этот эндпоинт поддерживает проект brew-browser — это отдельная зона доверия от постоянно доступных выше путей аналитики Homebrew."; +"settings.trendingHistory.offline" = "Офлайн-режим включён — переключатель заблокирован. Отключите офлайн-режим выше, чтобы разрешить загрузку истории."; +"settings.about.zeroTelemetryBody" = "brew-browser не собирает телеметрию, не обращается к своим серверам без необходимости и не использует учётные записи. Все исходящие сетевые запросы описаны в «Настройки → Сеть» и README и выполняются только для действий, которым они нужны."; +"about.closeAria" = "Закрыть окно «О приложении»"; +"about.tagline" = "Нативный интерфейс macOS для Homebrew."; +"about.creditsBody.beforeClaude" = " — многоагентный набор инструментов, который координировал волны работы (Backend Architect, Frontend Developer, Security Engineer, Code Reviewer, Technical Writer и другие), при участии "; +"about.creditsBody.afterClaude" = " в терминале, модель "; +"about.thanksLinux" = "Благодарим Homebrew за каталог пакетов, мейнтейнеров формул — за их работу, а Tauri — за нативную оболочку."; +"about.thanksMac" = "Благодарим Homebrew за каталог пакетов, мейнтейнеров формул и cask-пакетов — за их работу, а Tauri — за нативную оболочку."; +"about.posture" = "Никакой телеметрии. Никаких учётных записей. Все исходящие сетевые обращения описаны в «Настройки → Сеть»."; +"Repo" = "Репозиторий"; +"Built with" = "Создано с помощью"; +"Open GitHub Sponsors in your browser" = "Открыть GitHub Sponsors в браузере"; +"Close detail panel" = "Закрыть карточку пакета"; +"Loading package detail…" = "Загружаем карточку пакета…"; +"Couldn't load detail" = "Не удалось загрузить карточку"; +"Installed by User" = "Найдено вне Homebrew"; +"On request" = "Вручную"; +"Total on-disk size of the installed keg (all versions)." = "Общий размер установленного keg на диске (все версии)."; +"About categories" = "О категориях"; +"About this package's categories" = "О категориях этого пакета"; +"About tags" = "О тегах"; +"About these tags" = "Об этих тегах"; +"About this summary" = "Об описании"; +"detail.aboutCategories.body" = "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если категория выглядит неверно, и мы исправим её в следующем релизе."; +"detail.aboutTags.body" = "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если тег выглядит неверно, и мы исправим его в следующем релизе."; +"detail.aboutSummary.body" = "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если описание выглядит неверно, и мы исправим его в следующем релизе."; +"No longer available via Homebrew." = "Больше недоступно через Homebrew."; +"May be removed in a future Homebrew update." = "Может быть удалено при одном из будущих обновлений Homebrew."; +"Run brew upgrade for this package" = "Запустить brew upgrade для этого пакета"; +"Open advisory" = "Открыть бюллетень безопасности"; +"Patched version" = "Исправленная версия"; +"detail.trend.bootstrap" = "Начальные и ежедневные снимки — детализация растёт со временем"; +"detail.trend.daily" = "Ежедневные снимки установок"; +"Use cases" = "Сценарии использования"; +"About use cases" = "О сценариях использования"; +"About these use cases" = "Об этих сценариях использования"; +"detail.aboutUseCases.body" = "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если сценарии использования выглядят неверно, и мы исправим их в следующем релизе."; +"About similar packages" = "О похожих пакетах"; +"About these similar packages" = "Об этих похожих пакетах"; +"detail.aboutSimilar.body" = "Сгенерировано офлайн на этапе сборки Claude Haiku 4.5 — при использовании brew-browser сетевые запросы и LLM-запросы не выполняются. Сообщите о проблеме, если рекомендации выглядят неверно, и мы исправим их в следующем релизе."; +"GitHub repository statistics" = "Статистика репозитория GitHub"; +"Couldn't load GitHub stats" = "Не удалось загрузить статистику GitHub"; +"Dependencies" = "Зависимости"; +"Required by" = "Зависимые пакеты"; +"About reverse dependencies" = "Об обратных зависимостях"; +"detail.aboutReverseDependencies.body" = "Пакеты во встроенном каталоге, где этот пакет указан как зависимость. Вычисляется офлайн инверсией графа каталога — без сетевых запросов и вызовов brew. Зависимости только для сборки и опциональные/рекомендуемые связи помечены; остальное — обязательные runtime-зависимости."; +"Conflicts with" = "Конфликтует с"; +"detail.action.reinstall" = "Переустановить"; +"detail.category.openTitle" = "Показать все пакеты в категории «{category}»"; +"detail.caskUnavailableLinux" = "Cask-пакеты требуют macOS — этот пакет нельзя установить в Linux."; +"detail.deprecation.disabled.body" = "Этот пакет отключён и больше недоступен — он будет удалён."; +"detail.deprecation.deprecated.body" = "Этот пакет устарел и запланирован к удалению. Пока его ещё можно установить."; +"detail.deprecation.disabledDate.format" = "Отключён: %@"; +"detail.deprecation.deprecatedDate.format" = "Устарел: %@"; +"detail.deprecation.disabled.tooltip" = "Отключено — пакет больше недоступен и будет удалён."; +"detail.deprecation.deprecated.tooltip" = "Устарело — пока устанавливается, но запланировано к удалению."; +"detail.deprecation.useInstead" = "Используйте вместо него:"; +"about.title" = "О Brew Browser"; +"action.cancel" = "Отмена"; +"action.close" = "Закрыть"; +"action.deselectAll" = "Снять выбор"; +"action.installing" = "Устанавливаем…"; +"action.refresh" = "Обновить"; +"action.retry" = "Повторить"; +"action.selectAll" = "Выбрать все"; +"activity.collapse" = "Свернуть активность"; +"activity.copyOutput" = "Скопировать вывод"; +"activity.drawer.hide" = "Скрыть журнал активности"; +"activity.drawer.show" = "Показать журнал активности"; +"activity.expand" = "Развернуть активность"; +"activity.keepJobs.format" = "Хранить последние %d завершённых задач"; +"activity.limits.help" = "Новые ограничения применяются только к задачам, которые будут сохранены после изменения настройки. Уже сохранённая история не сокращается задним числом."; +"activity.linesPerJob.format" = "Хранить последние %d строк лога на задачу"; +"activity.notifyOnCompletion" = "Уведомлять о завершении задач"; +"activity.notifyOnCompletion.help" = "Показывает уведомление macOS, когда долгая задача brew завершается."; +"chrome.donate.help" = "Поддержать brew-browser через GitHub Sponsors"; +"chrome.donate.menu" = "Поддержать brew-browser…"; +"icon.source.faviconFrom" = "Favicon с {homepage}"; +"icon.source.homepage" = "домашняя страница"; +"icon.source.installedApp" = "установленное приложение"; +"icon.source.none" = "нет"; +"enrichment.fetchLatest" = "Обновлять категории и описания"; +"enrichment.fetchLatest.help" = "brew-browser поставляется со встроенными ИИ-категориями и описаниями. Если включено, приложение проверяет наличие более свежих данных проекта и получает описание пакета, когда вы открываете его карточку. Отправляется только имя просматриваемого пакета."; +"filter.all" = "Все"; +"filter.dependency" = "Зависимости"; +"filter.manual" = "Вручную"; +"filter.outdated" = "Есть обновления"; +"filter.vulnerable" = "Уязвимые"; +"github.connectedAs.format" = "GitHub: выполнен вход как @%@"; +"github.deviceFlowInstructions.format" = "Откройте %@ и введите этот код. Он уже скопирован в буфер обмена."; +"github.keychain.help" = "Токен хранится в macOS Keychain. Вход необязателен и использует минимальные разрешения для звёзд, отслеживания и создания issue в GitHub."; +"github.noInstalledHomepages" = "У установленных пакетов нет домашней страницы GitHub."; +"github.signIn" = "Вход"; +"github.signIn.help" = "Войдите в GitHub, чтобы ставить звёзды, отслеживать репозитории и создавать issue в GitHub по метаданным пакетов из brew-browser."; +"github.signInWithGitHub" = "Войти через GitHub"; +"github.signOut" = "Выйти"; +"github.signedInAs" = "Выполнен вход как"; +"github.signedInAsToast" = "Выполнен вход как @{username}"; +"github.signedInToast" = "Выполнен вход в GitHub"; +"github.signInDenied" = "Вход в GitHub отклонён"; +"github.signInExpired" = "Срок действия кода истёк"; +"github.signInExpiredBody" = "Попробуйте ещё раз."; +"github.stargazers" = "Звёзды"; +"github.forks" = "Форки"; +"github.licenseMismatchTitle" = "brew сообщает: {brew} · GitHub сообщает: {github}"; +"github.scopeRequired.title.format" = "Нужны дополнительные разрешения: %@"; +"github.scopeRequired.body.format" = "Не хватает разрешения GitHub «%@». Нажмите, чтобы выдать его без выхода из аккаунта."; +"github.yourCode" = "Ваш код"; +"brew.status.checking" = "Проверяем Homebrew…"; +"brew.status.installed" = "Homebrew установлен."; +"brew.status.notFound" = "brew не найден"; +"brew.status.notFoundOnPath" = "Homebrew не найден в PATH."; +"brew.status.ready.format" = "Homebrew %@ · prefix %@"; +"brew.status.unknown" = "Статус Homebrew неизвестен"; +"brew.status.unknownWithError" = "Статус Homebrew неизвестен — {error}"; +"catalog.refresh.noWritableDir" = "Не удалось найти доступный для записи каталог."; +"catalog.refresh.failed.format" = "Не удалось обновить каталог: %@"; +"catalog.refresh.malformedResponse" = "brew.sh вернул каталог в неожиданном формате."; +"catalog.refresh.emptyResponse.format" = "brew.sh вернул пустой каталог (формулы=%d, cask-пакеты=%d)."; +"catalog.refresh.compression" = "Не удалось сжать обновлённый каталог."; +"catalog.refresh.offlineBlocked" = "Офлайн-режим включён — обновление каталога заблокировано. Отключите его в разделе «Настройки → Сеть»."; +"catalog.refresh.brewFailed" = "brew завершился с ошибкой при обновлении каталога (код {code})."; +"menu.go" = "Переход"; +"package.kind.casks" = "Cask-пакеты"; +"package.kind.formulae" = "Формулы"; +"package.pinned" = "Закреплён"; +"palette.title.menu" = "Палитра команд…"; +"security.brewVulnsInstalled" = "brew-vulns установлен"; +"security.brewVulnsMissing.help" = "Для проверки уязвимостей нужна официальная подкоманда brew vulns."; +"security.installBrewVulns" = "Установить brew-vulns"; +"security.offlineSuppressed" = "Офлайн-режим включён — проверка уязвимостей заблокирована."; +"security.scanInstalled" = "Проверять установленные пакеты на известные уязвимости"; +"security.scanInstalled.help" = "Опционально, выключено по умолчанию. Запускает официальную подкоманду brew vulns для установленных формул и хранит результаты локально."; +"security.viewVulnerablePackages" = "Показать уязвимые пакеты →"; +"detail.security.cleanStale" = "На момент последней проверки известных уязвимостей нет — проверьте ещё раз для подтверждения"; +"detail.security.lastScan.format" = "Последняя проверка: %@"; +"detail.security.notScanned.detail" = "Проверка ещё не выполнялась. Проверка проходит по всем установленным пакетам: brew vulns не умеет проверять только один пакет."; +"detail.security.openAdvisory.format" = "Открыть бюллетень безопасности: %@"; +"detail.security.patchedIn.format" = "Исправлено в %@"; +"detail.security.rescanHelp" = "Запускает brew vulns по всей установленной библиотеке; отдельный пакет проверить нельзя"; +"onboarding.aria" = "Настройка Homebrew"; +"onboarding.title" = "Настроим Homebrew"; +"onboarding.body.mac" = "brew-browser — удобный интерфейс для Homebrew, пакетного менеджера для Mac. Установите Homebrew один раз, и библиотека появится здесь автоматически — перезапуск не нужен."; +"onboarding.body.linux" = "brew-browser — удобный интерфейс для Homebrew, который работает и в Linux. Установите Homebrew один раз, и библиотека появится здесь автоматически — перезапуск не нужен."; +"onboarding.clt.before" = "Сначала установите Xcode Command Line Tools: выполните"; +"onboarding.clt.after" = "в Терминале и следуйте подсказкам."; +"onboarding.linuxDeps.before" = "Сначала установите зависимости сборки Homebrew. Для Debian/Ubuntu:"; +"onboarding.linuxDeps.after" = "для других дистрибутивов см. страницу \"Homebrew on Linux\" на docs.brew.sh."; +"onboarding.installCopied" = "Команда установки скопирована"; +"onboarding.copyFailed" = "Не удалось скопировать команду"; +"onboarding.openTerminalFailed" = "Не удалось открыть Терминал"; +"onboarding.commandCopiedFallback" = "Команда установки скопирована — вставьте её в любой терминал."; +"onboarding.openTerminalOrCopyFailed" = "Не удалось открыть Терминал или скопировать команду"; +"services.alreadyRunning" = "Уже запущено"; +"services.empty.body" = "Установите, например, postgresql, redis или nginx — службы появятся здесь."; +"services.empty.title" = "Фоновых служб нет"; +"services.loadFailed" = "Не удалось загрузить службы"; +"services.loading" = "Загружаем службы brew…"; +"services.notRunning" = "Не запущено"; +"services.restart" = "Перезапустить службу"; +"services.start" = "Запустить службу"; +"services.status.error" = "ошибка"; +"services.status.notLoaded" = "не загружена"; +"services.status.running" = "запущена"; +"services.status.scheduled" = "запланирована"; +"services.status.stopped" = "остановлена"; +"services.status.unknown" = "неизвестно"; +"services.stop" = "Остановить службу"; +"settings.appearance.aiFeatures" = "Функции ИИ"; +"settings.appearance.aiFeatures.help" = "Если выключено, отображаются только штатные метаданные Homebrew. ИИ-категории и описания скрываются."; +"settings.appearance.defaultLanding" = "Стартовый раздел"; +"settings.brew.advanced" = "Дополнительно"; +"settings.brew.analytics" = "Аналитика"; +"settings.brew.analytics.help" = "Homebrew по умолчанию отправляет анонимную аналитику установок на formulae.brew.sh. Этот переключатель меняет собственную настройку аналитики Homebrew."; +"settings.brew.autoremove" = "Удалить лишние зависимости"; +"settings.brew.autoremove.confirmMessage" = "Будет выполнено brew autoremove: команда удалит формулы, установленные только как зависимости и больше не нужные ни одному пакету."; +"settings.brew.autoremove.confirmTitle" = "Удалить неиспользуемые зависимости?"; +"settings.brew.autoremove.failedToRun" = "Не удалось запустить brew autoremove"; +"settings.brew.autoremove.help" = "Запускает brew autoremove, чтобы удалить формулы, установленные только как зависимости и больше не нужные ни одному пакету."; +"settings.brew.autoremoveUnused" = "Удалить неиспользуемые зависимости"; +"settings.brew.confirmDestructive" = "Подтверждать удаление и zap-очистку"; +"settings.brew.confirmDestructive.help" = "Перед необратимыми действиями (удаление пакета, zap-очистка, удаление Brewfile) приложение запрашивает подтверждение."; +"settings.brew.confirmations" = "Подтверждения"; +"settings.brew.greedyUpgrades" = "Расширенное обновление"; +"settings.brew.greedyUpgrades.help" = "Включает cask-пакеты с собственным механизмом обновления, например Chrome, при запуске brew upgrade --greedy. По умолчанию выключено."; +"settings.brew.sendAnalytics" = "Отправлять аналитику установок Homebrew"; +"settings.fileUnreadable" = "Файл настроек не читается."; +"settings.github.showStats" = "Показывать статистику GitHub на страницах пакетов"; +"settings.github.showStats.help" = "После входа brew-browser проверяет, какие пакеты с домашними страницами GitHub вы отметили звёздочкой."; +"settings.network.caskIconFetching" = "Загрузка иконок cask-пакетов"; +"settings.network.catalogAutoRefresh" = "Автообновление каталога"; +"settings.network.catalogStaleThreshold.format" = "Показывать предупреждение об устаревшем каталоге через %d дней"; +"settings.network.offlineMode" = "Офлайн-режим"; +"settings.network.offlineMode.help" = "Блокирует все сетевые запросы brew-browser. Команды brew, которые вы запускаете сами, продолжают работать как обычно."; +"settings.network.offlineMode.on" = "Офлайн-режим включён"; +"settings.network.trendingTtl.format" = "TTL кэша трендов: %d мин."; +"settings.offlineToggleLocked" = "Офлайн-режим включён — переключатель заблокирован."; +"settings.option.daily" = "Ежедневно"; +"settings.option.installedOnly" = "Только установленные"; +"settings.option.off" = "Выкл."; +"settings.option.weekly" = "Еженедельно"; +"settings.resetToDefaults" = "Сбросить настройки"; +"settings.section.security" = "Безопасность"; +"settings.section.updates" = "Обновления"; +"table.actions" = "Действия"; +"table.name" = "Имя"; +"table.sortBy" = "Сортировать по столбцу «{label}»"; +"table.sortedAsc" = "Сортировка по столбцу «{label}» по возрастанию. Нажмите, чтобы изменить порядок."; +"table.sortedDesc" = "Сортировка по столбцу «{label}» по убыванию. Нажмите, чтобы изменить порядок."; +"table.status" = "Статус"; +"table.user" = "Пользователь"; +"dashboard.refresh.brewUpdateFailed" = "brew update завершился с ошибками"; +"dashboard.refresh.catalogFailed" = "Не удалось обновить каталог"; +"dashboard.refresh.failed" = "Не удалось обновить"; +"dashboard.refresh.successTitle" = "Обновлено"; +"dashboard.refresh.successBody" = "tap-репозитории brew, каталог и список установленных пакетов актуальны"; +"dashboard.doctor.failedToRun" = "Не удалось запустить brew doctor"; +"dashboard.upgradeAll.success" = "Обновлено пакетов: {count}"; +"storage.refreshDiskUsage" = "Обновить сведения о месте на диске"; +"storage.diskProbeFailed" = "Не удалось проверить диск: {error}"; +"storage.measuringDiskUsage" = "Считаем место на диске… (du -sk для {prefix})"; +"storage.notPresent" = "нет на диске"; +"storage.revealInFinder" = "Показать {path} в Finder"; +"storage.showInFileManager" = "Показать {path} в файловом менеджере"; +"storage.revealLabelInFinder" = "Показать «{label}» в Finder"; +"storage.showLabelInFileManager" = "Показать «{label}» в файловом менеджере"; +"storage.pathMissing" = "Путь не существует"; +"resize.panel" = "Изменить размер панели"; +"resize.dragHint" = "Потяните, чтобы изменить размер; двойной щелчок сбрасывает ширину"; +"theme.cycle" = "Переключить тему"; +"theme.label" = "Тема"; +"trending.fetchHistory" = "Загружать историю трендов"; +"trending.fetchHistory.help" = "Загружает историю установок по пакетам для мини-графиков и графиков в карточке пакета."; +"trending.sparklineAria" = "График динамики установок"; +"trending.error.backend" = "Бэкенд недоступен: {error}"; +"trending.error.failed" = "Не удалось загрузить тренды: {error}"; +"trending.error.network" = "Не удалось подключиться к formulae.brew.sh"; +"updates.autoCheck" = "Проверять ежедневно"; +"updates.autoCheck.help" = "Автоматически проверять обновления brew-browser раз в день."; +"updates.available.help" = "Нажмите, чтобы открыть обновление."; +"updates.channel" = "Канал"; +"updates.channel.label" = "Канал обновлений"; +"updates.channel.stable" = "Стабильный"; +"updates.check.help" = "Проверяет ленту Sparkle на наличие новой сборки brew-browser."; +"updates.checkForUpdates" = "Проверить обновления…"; +"updates.checkNow" = "Проверить сейчас"; +"updates.lastChecked.format" = "Последняя проверка: %@"; +"updates.never" = "Никогда"; +"updates.noBeta" = "В этом релизе нет бета-канала."; +"updates.offlineBlocked" = "Офлайн-режим блокирует проверку обновлений."; +"upgrade.choosePackages" = "Выберите пакеты для обновления"; +"upgrade.noOutdated" = "Устаревших пакетов нет."; +"upgrade.pinned.help" = "Закреплённые пакеты не обновляются через brew. Снимите закрепление в карточке пакета."; +"deprecated" = "устарело"; +"discover.activeFilters" = "Активные фильтры категорий"; +"discover.catalogFetched" = "Загружено с formulae.brew.sh"; +"discover.catalogRefreshed" = "Каталог обновлён"; +"discover.categoryBrowsingHidden" = "Обзор по категориям скрыт, потому что функции ИИ выключены в «Настройки → Оформление»."; +"discover.categoryTileAria" = "{category} — {count} пакетов"; +"discover.deprecatedDefault" = "Устарело — может быть удалено при одном из будущих обновлений Homebrew."; +"discover.deprecatedReason" = "Устарело: {reason}"; +"discover.disabledDefault" = "Отключено — больше недоступно через Homebrew."; +"discover.disabledReason" = "Отключено: {reason}"; +"discover.emptyFilter" = "Под этот фильтр пакеты не подходят."; +"discover.groupPackagesLabel" = "Пакеты в группе «{group}»"; +"discover.loadingCategories" = "Загружаем категории…"; +"discover.noResults" = "По запросу «{query}» ничего не найдено."; +"discover.noResults.body" = "Попробуйте более короткий или другой запрос."; +"discover.noResultsInCategories" = "По запросу «{query}» нет результатов в выбранных категориях."; +"discover.noResultsInCategories.body" = "Уберите фильтр категории или расширьте запрос."; +"discover.packagesInCategory" = "Пакеты в категории «{category}»"; +"discover.removeFilter" = "Убрать фильтр «{category}»"; +"discover.searchAria" = "Поиск по Homebrew"; +"discover.searchFailed" = "Поиск не удался"; +"discover.searchPlaceholder" = "Поиск в индексе Homebrew…"; +"discover.searchPromptTitle" = "Введите запрос для поиска в индексе Homebrew"; +"discover.staleDismissAria" = "Скрыть предупреждение об устаревшем каталоге"; +"discover.subgroupNote" = "сгруппировано по пересекающимся категориям"; +"discover.subgroupsBody" = "У Homebrew нет официальных подкатегорий, поэтому это группировка по совместной встречаемости: внутри «{category}» пакеты раскладываются по другим категориям, к которым они тоже относятся. Пакет с несколькими пересекающимися категориями попадает в каждую такую группу. «Общее: {category}» содержит пакеты, отмеченные только категорией «{category}». Это подсказка для навигации, а не строгая таксономия."; +"discover.subgroupsLabel" = "О подкатегориях"; +"discover.subgroupsTitle" = "Как устроены эти подгруппы"; +"package.installTrendFor" = "Динамика установок {name}"; +"package.openToken" = "Открыть {token}"; +"recovery.reason.alreadyInstalled" = "{name} уже установлен вне Homebrew."; +"recovery.reason.stillRequired" = "{name} ещё нужен другому установленному пакету."; +"vulnerabilities.scanFailed" = "Не удалось выполнить проверку уязвимостей"; +"vulnerabilities.scanOneFailed" = "Не удалось проверить уязвимости для {name}"; +"settings.github.corruptSettings" = "Файл настроек не читается — откройте раздел «Сеть», чтобы сбросить настройки."; +"settings.github.githubUserFallback" = "пользователь GitHub"; +"settings.github.privacy.afterStruct" = "."; +"settings.github.privacy.beforeStruct" = "brew-browser хранит токен в {keyringName}. Токен никогда не отправляется в renderer-процесс по IPC, никогда не записывается на диск и никогда не попадает в логи. Через IPC передаётся только производное представление "; +"settings.github.scopes.afterNotifications" = " для отслеживания. Доступа к приватным репозиториям, email и администрированию нет."; +"settings.github.scopes.afterPublicRepo" = " для звёзд и создания issue, а также "; +"settings.github.scopes.afterReadUser" = " для отображения имени пользователя, "; +"settings.github.scopes.beforeReadUser" = "Вход необязателен. Запрашиваются минимальные разрешения: "; +"settings.github.signInHint" = "Открывает стандартный поток GitHub «Authorize app» в браузере. Пароль никогда не вводится в brew-browser. Вход нужен, чтобы ставить звёзды, отслеживать репозитории и создавать issue в GitHub из brew-browser."; +"settings.github.signInPurpose" = "Для чего используется вход"; +"settings.github.signedInAs" = "Выполнен вход как"; +"settings.github.statsHint.afterHost" = "."; +"settings.github.statsHint.beforeHost" = "Показывает звёзды, форки и последний релиз для пакетов, у которых домашняя страница ведёт на GitHub. brew-browser загружает публичные метаданные репозитория из "; +"settings.github.scopesLabel" = "Разрешения"; diff --git a/native/Sources/BrewBrowserKit/ServicesView.swift b/native/Sources/BrewBrowserKit/ServicesView.swift index d3c8d83..e0fd7e1 100644 --- a/native/Sources/BrewBrowserKit/ServicesView.swift +++ b/native/Sources/BrewBrowserKit/ServicesView.swift @@ -28,14 +28,14 @@ struct ServicesView: View { private var headerBar: some View { HStack(spacing: 10) { if !model.services.isEmpty { - Text("\(runningCount) running · \(model.services.count) total") + Text(L10n.servicesSummary(running: runningCount, total: model.services.count)) .font(.callout).foregroundStyle(.secondary) } Spacer() Button { Task { await model.loadServices() } } label: { // Spinner replaces the arrow while refreshing (matches Trending). Label { - Text("Refresh") + Text(L10n.string("action.refresh")) } icon: { if model.servicesLoading { ProgressView().controlSize(.small) @@ -56,21 +56,21 @@ struct ServicesView: View { @ViewBuilder private var content: some View { if model.servicesLoading && model.services.isEmpty { - ProgressView("Loading brew services…") + ProgressView(L10n.string("services.loading")) .frame(maxWidth: .infinity, maxHeight: .infinity) } else if let err = model.servicesError, model.services.isEmpty { ContentUnavailableView { - Label("Couldn't load services", systemImage: "gearshape.2") + Label(L10n.string("services.loadFailed"), systemImage: "gearshape.2") } description: { Text(err) } actions: { - Button("Retry") { Task { await model.loadServices() } } + Button(L10n.string("action.retry")) { Task { await model.loadServices() } } } } else if model.services.isEmpty { ContentUnavailableView { - Label("No background services", systemImage: "gearshape.2") + Label(L10n.string("services.empty.title"), systemImage: "gearshape.2") } description: { - Text("Formulae like postgresql, redis, or nginx register background services you can start and stop here.") + Text(L10n.string("services.empty.body")) } } else { servicesTable @@ -79,15 +79,15 @@ struct ServicesView: View { private var servicesTable: some View { Table(model.sortedServices) { - TableColumn("Name") { svc in nameCell(svc) } + TableColumn(L10n.string("table.name")) { svc in nameCell(svc) } .width(min: 140, ideal: 240) - TableColumn("Status") { svc in ServiceStatusPill(status: svc.status) } + TableColumn(L10n.string("table.status")) { svc in ServiceStatusPill(status: svc.status) } .width(min: 92, ideal: 112) - TableColumn("User") { svc in + TableColumn(L10n.string("table.user")) { svc in Text(svc.user ?? "—").foregroundStyle(.secondary).lineLimit(1) } .width(min: 80, ideal: 130) - TableColumn("Actions") { svc in actionCell(svc) } + TableColumn(L10n.string("table.actions")) { svc in actionCell(svc) } .width(min: 108, ideal: 124) } } @@ -121,12 +121,12 @@ struct ServicesView: View { HStack(spacing: 8) { Button { act(.start, svc) } label: { Image(systemName: "play.fill") } .disabled(svc.status == .started) - .help(svc.status == .started ? "Already running" : "Start service") + .help(svc.status == .started ? L10n.string("services.alreadyRunning") : L10n.string("services.start")) Button { act(.stop, svc) } label: { Image(systemName: "stop.fill") } .disabled(svc.status == .stopped || svc.status == .notLoaded) - .help(svc.status == .stopped || svc.status == .notLoaded ? "Not running" : "Stop service") + .help(svc.status == .stopped || svc.status == .notLoaded ? L10n.string("services.notRunning") : L10n.string("services.stop")) Button { act(.restart, svc) } label: { Image(systemName: "arrow.clockwise") } - .help("Restart service") + .help(L10n.string("services.restart")) } .buttonStyle(.borderless) .foregroundStyle(.secondary) @@ -154,12 +154,12 @@ struct ServiceStatusPill: View { private var label: String { switch status { - case .started: return "running" - case .stopped: return "stopped" - case .notLoaded: return "not loaded" - case .error: return "error" - case .scheduled: return "scheduled" - case .unknown: return "unknown" + case .started: return L10n.string("services.status.running") + case .stopped: return L10n.string("services.status.stopped") + case .notLoaded: return L10n.string("services.status.notLoaded") + case .error: return L10n.string("services.status.error") + case .scheduled: return L10n.string("services.status.scheduled") + case .unknown: return L10n.string("services.status.unknown") } } diff --git a/native/Sources/BrewBrowserKit/SettingsView.swift b/native/Sources/BrewBrowserKit/SettingsView.swift index 4186215..19b3cb4 100644 --- a/native/Sources/BrewBrowserKit/SettingsView.swift +++ b/native/Sources/BrewBrowserKit/SettingsView.swift @@ -36,23 +36,23 @@ public struct SettingsView: View { public var body: some View { TabView(selection: $selected) { AppearanceSettings() - .tabItem { Label("Appearance", systemImage: "paintbrush") }.tag(SettingsTab.appearance.rawValue) + .tabItem { Label(L10n.string("settings.section.appearance"), systemImage: "paintbrush") }.tag(SettingsTab.appearance.rawValue) NetworkSettings() - .tabItem { Label("Network", systemImage: "globe") }.tag(SettingsTab.network.rawValue) + .tabItem { Label(L10n.string("settings.section.network"), systemImage: "globe") }.tag(SettingsTab.network.rawValue) GitHubSettings() .tabItem { Label("GitHub", systemImage: "chevron.left.forwardslash.chevron.right") }.tag(SettingsTab.github.rawValue) BrewSettings(model: model) .tabItem { Label("Brew", systemImage: "mug") }.tag(SettingsTab.brew.rawValue) UpdatesSettings(updater: updater) - .tabItem { Label("Updates", systemImage: "arrow.down.circle") }.tag(SettingsTab.updates.rawValue) + .tabItem { Label(L10n.string("settings.section.updates"), systemImage: "arrow.down.circle") }.tag(SettingsTab.updates.rawValue) VulnerabilitySettings() - .tabItem { Label("Security", systemImage: "shield") }.tag(SettingsTab.security.rawValue) + .tabItem { Label(L10n.string("settings.section.security"), systemImage: "shield") }.tag(SettingsTab.security.rawValue) TrendingSettings() - .tabItem { Label("Trending", systemImage: "chart.line.uptrend.xyaxis") }.tag(SettingsTab.trending.rawValue) + .tabItem { Label(L10n.string("nav.trending"), systemImage: "chart.line.uptrend.xyaxis") }.tag(SettingsTab.trending.rawValue) ActivitySettings() - .tabItem { Label("Activity", systemImage: "list.bullet.rectangle") }.tag(SettingsTab.activity.rawValue) + .tabItem { Label(L10n.string("nav.activity"), systemImage: "list.bullet.rectangle") }.tag(SettingsTab.activity.rawValue) AboutSettings() - .tabItem { Label("About", systemImage: "info.circle") }.tag(SettingsTab.about.rawValue) + .tabItem { Label(L10n.string("settings.section.about"), systemImage: "info.circle") }.tag(SettingsTab.about.rawValue) } .frame(width: 560, height: 480) } @@ -66,22 +66,22 @@ private struct AppearanceSettings: View { var body: some View { Form { - Picker("Theme", selection: $prefs.theme) { + Picker(L10n.string("theme.label"), selection: $prefs.theme) { ForEach(AppTheme.allCases, id: \.self) { Text($0.label).tag($0) } } .pickerStyle(.segmented) .onChange(of: prefs.theme) { _, _ in prefs.applyTheme() } - Picker("Default landing", selection: $prefs.defaultSection) { + Picker(L10n.string("settings.appearance.defaultLanding"), selection: $prefs.defaultSection) { ForEach(LandingSection.allCases, id: \.self) { Text($0.label).tag($0) } } SwiftUI.Section { - Toggle("AI features", isOn: Binding( + Toggle(L10n.string("settings.appearance.aiFeatures"), isOn: Binding( get: { settings.aiFeaturesEnabled }, set: { settings.aiFeaturesEnabled = $0; try? settings.save() } )) - Text("Shows extra metadata generated at build time: friendly names, descriptions, use-cases, similar packages, and category tags. Zero LLM calls are made from your machine — all enrichment is baked into the app. When off, only Homebrew's native metadata appears (categories are hidden).") + Text(L10n.string("settings.appearance.aiFeatures.help")) .font(.caption).foregroundStyle(.secondary) } } @@ -98,27 +98,27 @@ private struct NetworkSettings: View { var body: some View { Form { SwiftUI.Section { - Toggle("Offline Mode", isOn: Binding( + Toggle(L10n.string("settings.network.offlineMode"), isOn: Binding( get: { settings.paranoidMode }, set: { settings.paranoidMode = $0; try? settings.save() } )) - Text("Blocks every outbound network call: catalog refresh, Trending, GitHub stats + sign-in, cask icon probes, update checks. brew itself still runs normally. UI that needs the network shows a 'disabled by Offline Mode' notice.") + Text(L10n.string("settings.network.offlineMode.help")) .font(.caption).foregroundStyle(.secondary) if settings.paranoidMode { - Label("Offline Mode is on — Trending, Catalog refresh, and Cask icon probes are blocked.", + Label(L10n.string("settings.network.offlineMode.on"), systemImage: "exclamationmark.triangle.fill") .font(.caption).foregroundStyle(.orange) } } SwiftUI.Section { - Picker("Catalog auto-refresh", selection: Binding( + Picker(L10n.string("settings.network.catalogAutoRefresh"), selection: Binding( get: { settings.catalogAutoRefresh }, set: { settings.catalogAutoRefresh = $0; try? settings.save() } )) { - Text("Off").tag(CatalogAutoRefresh.off) - Text("Weekly").tag(CatalogAutoRefresh.weekly) - Text("Daily").tag(CatalogAutoRefresh.daily) + Text(L10n.string("settings.option.off")).tag(CatalogAutoRefresh.off) + Text(L10n.string("settings.option.weekly")).tag(CatalogAutoRefresh.weekly) + Text(L10n.string("settings.option.daily")).tag(CatalogAutoRefresh.daily) } .pickerStyle(.segmented) .disabled(settings.isCorrupt) @@ -127,16 +127,16 @@ private struct NetworkSettings: View { get: { Int(settings.catalogStaleBannerDays) }, set: { settings.catalogStaleBannerDays = UInt32($0); try? settings.save() } ), in: 1...365) { - Text("Catalog stale-banner threshold: \(settings.catalogStaleBannerDays) days") + Text(String(format: L10n.string("settings.network.catalogStaleThreshold.format"), Int(settings.catalogStaleBannerDays))) } - Picker("Cask icon fetching", selection: Binding( + Picker(L10n.string("settings.network.caskIconFetching"), selection: Binding( get: { settings.caskIconMode }, set: { settings.caskIconMode = $0; try? settings.save() } )) { - Text("Off").tag(CaskIconMode.off) - Text("Installed only").tag(CaskIconMode.installedOnly) - Text("All").tag(CaskIconMode.all) + Text(L10n.string("settings.option.off")).tag(CaskIconMode.off) + Text(L10n.string("settings.option.installedOnly")).tag(CaskIconMode.installedOnly) + Text(L10n.string("filter.all")).tag(CaskIconMode.all) } .pickerStyle(.segmented) @@ -144,15 +144,15 @@ private struct NetworkSettings: View { get: { Int(settings.trendingTtlMinutes) }, set: { settings.trendingTtlMinutes = UInt32($0); try? settings.save() } ), in: 5...1440, step: 5) { - Text("Trending cache TTL: \(settings.trendingTtlMinutes) min") + Text(String(format: L10n.string("settings.network.trendingTtl.format"), Int(settings.trendingTtlMinutes))) } } if settings.isCorrupt { SwiftUI.Section { - Label("Settings file is unreadable.", systemImage: "exclamationmark.octagon.fill") + Label(L10n.string("settings.fileUnreadable"), systemImage: "exclamationmark.octagon.fill") .foregroundStyle(.red) - Button("Reset to defaults") { settings.reset() } + Button(L10n.string("settings.resetToDefaults")) { settings.reset() } } } } @@ -174,41 +174,41 @@ private struct GitHubSettings: View { var body: some View { Form { SwiftUI.Section { - Toggle("Show GitHub stats on package pages", isOn: Binding( + Toggle(L10n.string("settings.github.showStats"), isOn: Binding( get: { settings.githubEnabled }, set: { settings.githubEnabled = $0; try? settings.save() } )) - Text("Show repo stars, forks, and last release for any package whose homepage is a GitHub URL. Fetches public metadata from api.github.com.") + Text(L10n.string("settings.github.showStats.help")) .font(.caption).foregroundStyle(.secondary) } - SwiftUI.Section("Sign in") { + SwiftUI.Section(L10n.string("github.signIn")) { if let status, status.signedIn { - LabeledContent("Signed in as", value: "@\(status.username ?? "?")") + LabeledContent(L10n.string("github.signedInAs"), value: "@\(status.username ?? "?")") if !status.scopes.isEmpty { - LabeledContent("Scopes", value: status.scopes.joined(separator: ", ")) + LabeledContent(L10n.isRussian ? "Разрешения" : "Scopes", value: status.scopes.joined(separator: ", ")) .font(.caption) } - Button("Sign out") { + Button(L10n.string("github.signOut")) { Task { github.signOut() self.status = github.status() } } } else if let flow { - LabeledContent("Your code", value: flow.userCode) + LabeledContent(L10n.string("github.yourCode"), value: flow.userCode) .font(.body.monospaced()) - Text("Enter this code at \(flow.verificationUri) (opened in your browser). Waiting for authorization…") + Text(String(format: L10n.string("github.deviceFlowInstructions.format"), flow.verificationUri)) .font(.caption).foregroundStyle(.secondary) ProgressView().controlSize(.small) } else { Button { Task { await startSignIn() } } label: { - Label("Sign in with GitHub", systemImage: "person.crop.circle.badge.plus") + Label(L10n.string("github.signInWithGitHub"), systemImage: "person.crop.circle.badge.plus") } .disabled(signingIn) - Text("Opens GitHub's standard authorize flow in your browser. No password is entered into brew-browser. Required to Star, Watch, and file issues.") + Text(L10n.string("github.signIn.help")) .font(.caption).foregroundStyle(.secondary) } if let error { @@ -217,7 +217,7 @@ private struct GitHubSettings: View { } SwiftUI.Section { - Text("Your token is stored in the macOS Keychain — never sent over IPC, written to disk, or logged. Sign-in is optional; minimum scopes are read:user, public_repo, and notifications.") + Text(L10n.string("github.keychain.help")) .font(.caption).foregroundStyle(.secondary) } } @@ -259,8 +259,8 @@ private struct BrewSettings: View { var body: some View { Form { - SwiftUI.Section("Analytics") { - Toggle("Send Homebrew install analytics", isOn: Binding( + SwiftUI.Section(L10n.string("settings.brew.analytics")) { + Toggle(L10n.string("settings.brew.sendAnalytics"), isOn: Binding( get: { analytics ?? false }, set: { newValue in Task { @@ -272,37 +272,37 @@ private struct BrewSettings: View { } )) .disabled(analytics == nil || analyticsBusy) - Text("Homebrew sends anonymous install analytics to formulae.brew.sh by default. This flips Homebrew's own setting (same as `brew analytics on`/`off`).") + Text(L10n.string("settings.brew.analytics.help")) .font(.caption).foregroundStyle(.secondary) } - SwiftUI.Section("Confirmations") { - Toggle("Confirm before uninstall / zap", isOn: $prefs.confirmDestructive) - Text("Destructive actions (Uninstall, Zap, Delete Brewfile) ask before proceeding.") + SwiftUI.Section(L10n.string("settings.brew.confirmations")) { + Toggle(L10n.string("settings.brew.confirmDestructive"), isOn: $prefs.confirmDestructive) + Text(L10n.string("settings.brew.confirmDestructive.help")) .font(.caption).foregroundStyle(.secondary) } - SwiftUI.Section("Advanced") { - Toggle("Greedy upgrades (include self-updating casks)", isOn: $prefs.greedyUpgrade) - Text("Adds `--greedy` to `brew upgrade` so casks that update themselves (like Chrome) are upgraded too. Off by default — greedy can churn apps that manage their own updates.") + SwiftUI.Section(L10n.string("settings.brew.advanced")) { + Toggle(L10n.string("settings.brew.greedyUpgrades"), isOn: $prefs.greedyUpgrade) + Text(L10n.string("settings.brew.greedyUpgrades.help")) .font(.caption).foregroundStyle(.secondary) - Button("Autoremove unused dependencies") { + Button(L10n.string("settings.brew.autoremoveUnused")) { if prefs.confirmDestructive { confirmAutoremove = true } else { Task { await model.autoremove() } } } - Text("Runs `brew autoremove` to uninstall formulae that were installed only as dependencies and are no longer needed by anything.") + Text(L10n.string("settings.brew.autoremove.help")) .font(.caption).foregroundStyle(.secondary) } } .formStyle(.grouped) .padding(20) .task { analytics = await brew.getAnalytics() } - .confirmationDialog("Remove unused dependencies?", isPresented: $confirmAutoremove, titleVisibility: .visible) { - Button("Autoremove", role: .destructive) { Task { await model.autoremove() } } - Button("Cancel", role: .cancel) {} + .confirmationDialog(L10n.string("settings.brew.autoremove.confirmTitle"), isPresented: $confirmAutoremove, titleVisibility: .visible) { + Button(L10n.string("settings.brew.autoremove"), role: .destructive) { Task { await model.autoremove() } } + Button(L10n.string("action.cancel"), role: .cancel) {} } message: { - Text("This runs `brew autoremove`, which uninstalls formulae that were installed only as dependencies and are no longer required by anything else.") + Text(L10n.string("settings.brew.autoremove.confirmMessage")) } } } @@ -330,19 +330,19 @@ private struct UpdatesSettings: View { Button { updater.checkForUpdates() } label: { - Label("Check now", systemImage: "arrow.clockwise") + Label(L10n.string("updates.checkNow"), systemImage: "arrow.clockwise") } .disabled(offline || !updater.canCheckForUpdates) Spacer() - Text("Last checked: \(lastCheckedLabel)") + Text(String(format: L10n.string("updates.lastChecked.format"), lastCheckedLabel)) .font(.caption).foregroundStyle(.secondary) } if offline { - Label("Offline Mode is on — manual update checks are blocked. Turn it off in Network to check the feed.", + Label(L10n.string("updates.offlineBlocked"), systemImage: "exclamationmark.triangle.fill") .font(.caption).foregroundStyle(.orange) } else { - Text("Fetches brew-browser.zerologic.com/appcast.xml and compares the published version to the one you're running. No version number is sent.") + Text(L10n.string("updates.check.help")) .font(.caption).foregroundStyle(.secondary) } } @@ -352,14 +352,14 @@ private struct UpdatesSettings: View { // Mode so the preference is set for the next time it's off, matching // the Tauri auto-check toggle and every other network toggle. SwiftUI.Section { - Toggle("Automatically check for updates", isOn: $updater.automaticallyChecksForUpdates) - Text("When on, brew-browser checks for a newer version once a day and shows a notice in the title bar if one is available. Suspended while Offline Mode is on.") + Toggle(L10n.string("updates.autoCheck"), isOn: $updater.automaticallyChecksForUpdates) + Text(L10n.string("updates.autoCheck.help")) .font(.caption).foregroundStyle(.secondary) } - SwiftUI.Section("Update channel") { - LabeledContent("Channel", value: "Stable") - Text("No beta channel in this release.") + SwiftUI.Section(L10n.string("updates.channel")) { + LabeledContent(L10n.string("updates.channel.label"), value: L10n.string("updates.channel.stable")) + Text(L10n.string("updates.noBeta")) .font(.caption).foregroundStyle(.secondary) } } @@ -370,7 +370,7 @@ private struct UpdatesSettings: View { /// Last-checked relative date ("2 hours ago"), or "Never" before the first /// check. Mirrors the Tauri `lastCheckedLabel`. private var lastCheckedLabel: String { - guard let date = updater.lastUpdateCheckDate else { return "Never" } + guard let date = updater.lastUpdateCheckDate else { return L10n.string("updates.never") } return date.formatted(.relative(presentation: .named)) } } @@ -388,15 +388,15 @@ private struct VulnerabilitySettings: View { var body: some View { Form { SwiftUI.Section { - Toggle("Scan installed packages for known vulnerabilities", isOn: Binding( + Toggle(L10n.string("security.scanInstalled"), isOn: Binding( get: { settings.vulnerabilityScanningEnabled }, set: { settings.vulnerabilityScanningEnabled = $0; try? settings.save() } )) .disabled(offline || settings.isCorrupt) - Text("Opt-in, off by default. Shells out to the official `brew vulns` subcommand, which queries OSV.dev for known vulnerabilities in your installed formulae. If signed in to GitHub, GHSA IDs are enriched from api.github.com. No package list leaves your machine except the queries brew vulns makes.") + Text(L10n.string("security.scanInstalled.help")) .font(.caption).foregroundStyle(.secondary) if offline { - Label("Offline Mode is on — scanning is suppressed even if this is on.", + Label(L10n.string("security.offlineSuppressed"), systemImage: "exclamationmark.triangle.fill") .font(.caption).foregroundStyle(.orange) } @@ -405,7 +405,7 @@ private struct VulnerabilitySettings: View { if settings.vulnerabilityScanningEnabled && !offline { SwiftUI.Section { if helperInstalled == false { - Text("The brew-vulns subcommand isn't installed. Install it now? This runs `brew install homebrew/brew-vulns/brew-vulns`.") + Text(L10n.string("security.brewVulnsMissing.help")) .font(.caption).foregroundStyle(.secondary) Button { Task { @@ -416,14 +416,14 @@ private struct VulnerabilitySettings: View { } } label: { if installing { - HStack { ProgressView().controlSize(.small); Text("Installing…") } + HStack { ProgressView().controlSize(.small); Text(L10n.string("action.installing")) } } else { - Label("Install brew-vulns", systemImage: "arrow.down.circle") + Label(L10n.string("security.installBrewVulns"), systemImage: "arrow.down.circle") } } .disabled(installing) } else if helperInstalled == true { - Label("brew vulns is installed.", systemImage: "checkmark.seal.fill") + Label(L10n.string("security.brewVulnsInstalled"), systemImage: "checkmark.seal.fill") .foregroundStyle(.green) } else { ProgressView().controlSize(.small) @@ -446,30 +446,30 @@ private struct TrendingSettings: View { var body: some View { Form { SwiftUI.Section { - Toggle("Fetch trending history", isOn: Binding( + Toggle(L10n.string("trending.fetchHistory"), isOn: Binding( get: { settings.enhancedTrendingEnabled }, set: { settings.enhancedTrendingEnabled = $0; try? settings.save() } )) .disabled(offline || settings.isCorrupt) - Text("When on, brew-browser fetches per-package historical install trends from brew-browser.zerologic.com/trending-history/* to power sparklines on Trending and each package's detail panel. Only the package name you're viewing is sent. Operated by the brew-browser project — a distinct trust boundary from the Homebrew analytics paths.") + Text(L10n.string("trending.fetchHistory.help")) .font(.caption).foregroundStyle(.secondary) if offline { - Label("Offline Mode is on — this toggle is locked off.", + Label(L10n.string("settings.offlineToggleLocked"), systemImage: "exclamationmark.triangle.fill") .font(.caption).foregroundStyle(.orange) } } SwiftUI.Section { - Toggle("Fetch latest categories & descriptions", isOn: Binding( + Toggle(L10n.string("enrichment.fetchLatest"), isOn: Binding( get: { settings.liveEnrichmentEnabled }, set: { settings.liveEnrichmentEnabled = $0; try? settings.save() } )) .disabled(offline || settings.isCorrupt) - Text("brew-browser ships with built-in AI categories and descriptions. When on, it refreshes them from brew-browser.zerologic.com/enrichment/* — a version check on refresh, the full category list when newer, and a per-package description when you open its detail. Only the package name you're viewing is sent. Same first-party host as Enhanced Trending, a distinct /enrichment/* path. Requires AI features on.") + Text(L10n.string("enrichment.fetchLatest.help")) .font(.caption).foregroundStyle(.secondary) if offline { - Label("Offline Mode is on — this toggle is locked off.", + Label(L10n.string("settings.offlineToggleLocked"), systemImage: "exclamationmark.triangle.fill") .font(.caption).foregroundStyle(.orange) } @@ -489,24 +489,24 @@ private struct ActivitySettings: View { Form { SwiftUI.Section { Stepper(value: $prefs.activityMaxJobs, in: 1...1000) { - Text("Keep last \(prefs.activityMaxJobs) completed jobs") + Text(String(format: L10n.string("activity.keepJobs.format"), prefs.activityMaxJobs)) } Stepper(value: $prefs.activityMaxLines, in: 100...10000, step: 50) { - Text("Lines per job: \(prefs.activityMaxLines)") + Text(String(format: L10n.string("activity.linesPerJob.format"), prefs.activityMaxLines)) } - Text("These limits apply to future job persistence. Existing retained data is not trimmed retroactively.") + Text(L10n.string("activity.limits.help")) .font(.caption).foregroundStyle(.secondary) } SwiftUI.Section { - Toggle("Notify when brew tasks finish", isOn: Binding( + Toggle(L10n.string("activity.notifyOnCompletion"), isOn: Binding( get: { prefs.notifyOnTaskCompletion }, set: { on in prefs.notifyOnTaskCompletion = on if on { NotificationService.requestAuthorization() } } )) - Text("Posts a macOS notification when an install, upgrade, or other brew task finishes while brew-browser isn't the front app. When it's in front, the Activity drawer shows progress instead. Turning this on asks macOS for notification permission.") + Text(L10n.string("activity.notifyOnCompletion.help")) .font(.caption).foregroundStyle(.secondary) } } @@ -521,13 +521,13 @@ private struct AboutSettings: View { var body: some View { Form { SwiftUI.Section { - LabeledContent("App version", value: appVersion()) - LabeledContent("License", value: "MIT") + LabeledContent(L10n.string("App version"), value: appVersion()) + LabeledContent(L10n.string("License"), value: "MIT") Link("github.com/msitarzewski/brew-browser", destination: URL(string: "https://github.com/msitarzewski/brew-browser")!) } SwiftUI.Section { - Text("Zero telemetry. Zero accounts. brew-browser does not collect telemetry, phone home, or have user accounts. Every outbound request is documented in Settings → Network and only fires when you take an action that requires it.") + Text(L10n.string("settings.about.zeroTelemetryBody")) .font(.caption).foregroundStyle(.secondary) } } diff --git a/native/Sources/BrewBrowserKit/SnapshotStore.swift b/native/Sources/BrewBrowserKit/SnapshotStore.swift index e98806a..1d4ebcf 100644 --- a/native/Sources/BrewBrowserKit/SnapshotStore.swift +++ b/native/Sources/BrewBrowserKit/SnapshotStore.swift @@ -237,9 +237,34 @@ enum SnapshotError: LocalizedError { var errorDescription: String? { switch self { - case .notFound(let id): return "Snapshot \"\(id)\" not found." - case .unsafePath(let why): return why - case .invalidID(let why): return why + case .notFound(let id): + return L10n.isRussian ? "Снимок «\(id)» не найден." : "Snapshot \"\(id)\" not found." + case .unsafePath(let why): + if !L10n.isRussian { return why } + switch why { + case "refusing to import a symlink": + return "Нельзя импортировать символическую ссылку." + case "file larger than 1 MiB": + return "Файл больше 1 МиБ." + case "refusing to import binary content": + return "Нельзя импортировать бинарный файл." + default: + if why.hasPrefix("refusing to write inside ") { + let path = String(why.dropFirst("refusing to write inside ".count)) + return "Нельзя записывать снимок внутрь \(path)." + } + return why + } + case .invalidID(let why): + if !L10n.isRussian { return why } + if why == "Snapshot id must be 1–64 characters." { + return "Идентификатор снимка должен содержать от 1 до 64 символов." + } + if why.hasPrefix("Snapshot id contains an illegal character: ") { + let id = String(why.dropFirst("Snapshot id contains an illegal character: ".count)) + return "Идентификатор снимка содержит недопустимый символ: \(id)." + } + return why } } } diff --git a/native/Sources/BrewBrowserKit/SnapshotsView.swift b/native/Sources/BrewBrowserKit/SnapshotsView.swift index 1d6c082..7793c73 100644 --- a/native/Sources/BrewBrowserKit/SnapshotsView.swift +++ b/native/Sources/BrewBrowserKit/SnapshotsView.swift @@ -26,25 +26,25 @@ struct SnapshotsView: View { .task { await model.loadSnapshots() } .sheet(isPresented: $showNewSheet) { newSheet } .confirmationDialog( - toRestore.map { "Restore from “\($0.label)”?" } ?? "", + toRestore.map { L10n.isRussian ? "Восстановить из снимка «\($0.label)»?" : "Restore from “\($0.label)”?" } ?? "", isPresented: restoreBinding, titleVisibility: .visible, presenting: toRestore ) { snap in - Button("Restore") { toRestore = nil; Task { await model.restoreSnapshot(snap) } } - Button("Cancel", role: .cancel) { toRestore = nil } + Button(L10n.string("Restore")) { toRestore = nil; Task { await model.restoreSnapshot(snap) } } + Button(L10n.string("Cancel"), role: .cancel) { toRestore = nil } } message: { _ in - Text("This installs packages from the snapshot. Existing packages are skipped.") + Text(L10n.string("This installs packages from the snapshot. Existing packages are skipped.")) } .confirmationDialog( - toDelete.map { "Delete snapshot “\($0.label)”?" } ?? "", + toDelete.map { L10n.isRussian ? "Удалить снимок «\($0.label)»?" : "Delete snapshot “\($0.label)”?" } ?? "", isPresented: deleteBinding, titleVisibility: .visible, presenting: toDelete ) { snap in - Button("Delete", role: .destructive) { toDelete = nil; Task { await model.deleteSnapshot(snap) } } - Button("Cancel", role: .cancel) { toDelete = nil } + Button(L10n.string("Delete"), role: .destructive) { toDelete = nil; Task { await model.deleteSnapshot(snap) } } + Button(L10n.string("Cancel"), role: .cancel) { toDelete = nil } } message: { _ in - Text("The Brewfile will be removed from disk. This cannot be undone.") + Text(L10n.string("The Brewfile will be removed from disk. This cannot be undone.")) } - .alert("Snapshot error", isPresented: errorBinding, presenting: errorMessage) { _ in - Button("OK", role: .cancel) { errorMessage = nil } + .alert(L10n.string("Snapshot error"), isPresented: errorBinding, presenting: errorMessage) { _ in + Button(L10n.string("OK"), role: .cancel) { errorMessage = nil } } message: { msg in Text(msg) } } @@ -53,10 +53,10 @@ struct SnapshotsView: View { HStack(spacing: 10) { Spacer() Button { importSnapshot() } label: { - Label("Import…", systemImage: "square.and.arrow.down") + Label(L10n.string("Import…"), systemImage: "square.and.arrow.down") } Button { newLabel = defaultLabel(); showNewSheet = true } label: { - Label("New Snapshot", systemImage: "plus") + Label(L10n.string("New Snapshot"), systemImage: "plus") } .buttonStyle(.borderedProminent) } @@ -67,13 +67,13 @@ struct SnapshotsView: View { @ViewBuilder private var content: some View { if model.snapshotsLoading && model.snapshots.isEmpty { - ProgressView("Loading snapshots…") + ProgressView(L10n.string("Loading snapshots…")) .frame(maxWidth: .infinity, maxHeight: .infinity) } else if model.snapshots.isEmpty { ContentUnavailableView { - Label("No snapshots yet", systemImage: "archivebox") + Label(L10n.string("No snapshots yet"), systemImage: "archivebox") } description: { - Text("Save your current setup so you can restore it on another Mac. Snapshots live in ~/Library/Application Support/brew-browser/brewfiles/ — findable outside the app too.") + Text(L10n.string("Save your current setup so you can restore it on another Mac. Snapshots live in ~/Library/Application Support/brew-browser/brewfiles/ — findable outside the app too.")) } } else { ScrollView { @@ -97,15 +97,15 @@ struct SnapshotsView: View { Spacer(minLength: 12) HStack(spacing: 8) { Button { toRestore = snap } label: { - Label("Restore", systemImage: "arrow.counterclockwise") + Label(L10n.string("Restore"), systemImage: "arrow.counterclockwise") } .buttonStyle(.borderedProminent).controlSize(.small) Button { exportSnapshot(snap) } label: { - Label("Export…", systemImage: "square.and.arrow.up") + Label(L10n.string("Export…"), systemImage: "square.and.arrow.up") } .controlSize(.small) Button(role: .destructive) { toDelete = snap } label: { - Label("Delete", systemImage: "trash") + Label(L10n.string("Delete"), systemImage: "trash") } .controlSize(.small) } @@ -121,29 +121,34 @@ struct SnapshotsView: View { private func metaLine(_ snap: Snapshot) -> String { var parts = [ snap.createdAt.formatted(date: .abbreviated, time: .shortened), - "\(snap.counts.formulae) formulae", - "\(snap.counts.casks) casks", + L10n.formulaeCount(snap.counts.formulae), + L10n.casksCount(snap.counts.casks), ] - if snap.counts.masApps > 0 { parts.append("\(snap.counts.masApps) MAS apps") } + if snap.counts.masApps > 0 { + let n = snap.counts.masApps + parts.append(L10n.isRussian + ? "\(n) \(L10n.ruPlural(n, one: "приложение MAS", few: "приложения MAS", many: "приложений MAS"))" + : "\(n) MAS apps") + } return parts.joined(separator: " · ") } // New-snapshot sheet. private var newSheet: some View { VStack(alignment: .leading, spacing: 16) { - Text("New Snapshot").font(.headline) + Text(L10n.string("New Snapshot")).font(.headline) VStack(alignment: .leading, spacing: 6) { - Text("Name").font(.caption).foregroundStyle(.secondary) - TextField("snapshot-name", text: $newLabel) + Text(L10n.string("Name")).font(.caption).foregroundStyle(.secondary) + TextField(L10n.isRussian ? "Имя снимка" : "snapshot-name", text: $newLabel) .textFieldStyle(.roundedBorder) .onSubmit(create) } - Text("Stored in ~/Library/Application Support/brew-browser/brewfiles/") + Text(L10n.string("Stored in ~/Library/Application Support/brew-browser/brewfiles/")) .font(.caption).foregroundStyle(.secondary) HStack { Spacer() - Button("Cancel") { showNewSheet = false }.keyboardShortcut(.cancelAction) - Button("Create", action: create) + Button(L10n.string("Cancel")) { showNewSheet = false }.keyboardShortcut(.cancelAction) + Button(L10n.string("Create"), action: create) .buttonStyle(.borderedProminent) .keyboardShortcut(.defaultAction) .disabled(newLabel.trimmingCharacters(in: .whitespaces).isEmpty) diff --git a/native/Sources/BrewBrowserKit/Toast.swift b/native/Sources/BrewBrowserKit/Toast.swift index 5b1fe18..85bb08b 100644 --- a/native/Sources/BrewBrowserKit/Toast.swift +++ b/native/Sources/BrewBrowserKit/Toast.swift @@ -124,7 +124,7 @@ private struct ToastCard: View { } .buttonStyle(.plain) .foregroundStyle(.secondary) - .help("Dismiss") + .help(L10n.string("Dismiss")) } .padding(12) .frame(width: 320, alignment: .leading) diff --git a/native/Sources/BrewBrowserKit/TrendingService.swift b/native/Sources/BrewBrowserKit/TrendingService.swift index f90a4de..973e69b 100644 --- a/native/Sources/BrewBrowserKit/TrendingService.swift +++ b/native/Sources/BrewBrowserKit/TrendingService.swift @@ -6,7 +6,14 @@ enum TrendingWindow: String, CaseIterable, Identifiable, Sendable { case d90 = "90d" case d365 = "365d" var id: String { rawValue } - var label: String { rawValue } + var label: String { + guard L10n.isRussian else { return rawValue } + switch self { + case .d30: return "30 дн." + case .d90: return "90 дн." + case .d365: return "365 дн." + } + } } /// One entry from the always-on Homebrew install analytics — token + install diff --git a/native/Sources/BrewBrowserKit/TrendingView.swift b/native/Sources/BrewBrowserKit/TrendingView.swift index 956d1d7..585808f 100644 --- a/native/Sources/BrewBrowserKit/TrendingView.swift +++ b/native/Sources/BrewBrowserKit/TrendingView.swift @@ -14,7 +14,7 @@ struct TrendingView: View { var body: some View { Group { if model.trendingLoading && model.trendingEntries.isEmpty { - ProgressView("Fetching install counts from formulae.brew.sh…") + ProgressView(L10n.string("Fetching install counts from formulae.brew.sh…")) .frame(maxWidth: .infinity, maxHeight: .infinity) } else { VStack(spacing: 0) { @@ -36,7 +36,7 @@ struct TrendingView: View { // parity). The picker is centered via overlay so the trailing cluster // doesn't shift it off-center. private var windowBar: some View { - Picker("Window", selection: Binding( + Picker(L10n.isRussian ? "Период" : "Window", selection: Binding( get: { model.trendingWindow }, set: { w in Task { await model.setTrendingWindow(w) } } )) { @@ -59,7 +59,7 @@ struct TrendingView: View { // Spinner replaces the arrow while fetching so a click reads // as "working" even when the refresh returns quickly. Label { - Text("Refresh") + Text(L10n.string("Refresh")) } icon: { if model.trendingLoading { ProgressView().controlSize(.small) @@ -80,11 +80,8 @@ struct TrendingView: View { private var updatedLabel: String? { guard let at = model.trendingFetchedAt else { return nil } let secs = Int(max(0, Date().timeIntervalSince1970 - at)) - let ago: String - if secs < 60 { ago = "\(secs)s" } - else if secs < 3600 { ago = "\(secs / 60)m" } - else { ago = "\(secs / 3600)h" } - return "Updated \(ago) ago" + let ago = L10n.shortElapsed(seconds: secs) + return L10n.isRussian ? "Обновлено \(ago) назад" : "Updated \(ago) ago" } @ViewBuilder @@ -92,11 +89,11 @@ struct TrendingView: View { let rows = model.sortedTrendingRows if rows.isEmpty { ContentUnavailableView( - "Couldn't load trending", + L10n.string("Couldn't load trending"), systemImage: "chart.line.uptrend.xyaxis", description: Text(model.settings.paranoidMode - ? "Offline Mode is on — trending needs formulae.brew.sh." - : "formulae.brew.sh returned no entries.") + ? L10n.string("Offline Mode is on — trending needs formulae.brew.sh.") + : L10n.string("formulae.brew.sh returned no entries.")) ) } else { // Velocity is ALWAYS shown (computed from analytics, like Tauri). @@ -125,24 +122,24 @@ struct TrendingView: View { Text("\(r.rank)").foregroundStyle(.secondary).monospacedDigit() }.width(min: 32, ideal: 40) - TableColumn("Name", value: \.name) { nameCell($0) }.width(min: 130, ideal: 190) + TableColumn(L10n.string("Name"), value: \.name) { nameCell($0) }.width(min: 130, ideal: 190) if desc { - TableColumn("Description", value: \.summary) { r in + TableColumn(L10n.string("Description"), value: \.summary) { r in Text(r.summary).foregroundStyle(.secondary).lineLimit(1) }.width(min: 140, ideal: 260) } - TableColumn("Version", value: \.version) { r in + TableColumn(L10n.string("Version"), value: \.version) { r in Text(r.version).foregroundStyle(.secondary).monospacedDigit().lineLimit(1) }.width(min: 60, ideal: 90) - TableColumn("Type", value: \.kind.rawValue) { KindPill(kind: $0.kind) }.width(min: 56, ideal: 72) + TableColumn(L10n.string("Type"), value: \.kind.rawValue) { KindPill(kind: $0.kind) }.width(min: 56, ideal: 72) // Velocity is always shown — computed from the analytics windows. - TableColumn("Velocity", value: \.velocityRank) { velocityCell($0) }.width(min: 64, ideal: 84) + TableColumn(L10n.string("Velocity"), value: \.velocityRank) { velocityCell($0) }.width(min: 64, ideal: 84) - TableColumn("Installs", value: \.installCount) { r in + TableColumn(L10n.string("Installs"), value: \.installCount) { r in // Stacked: count on top, sparkline (opt-in) beneath (matches Tauri). VStack(alignment: .trailing, spacing: 2) { Text(r.installCount, format: .number).monospacedDigit() @@ -161,9 +158,9 @@ struct TrendingView: View { .frame(maxWidth: .infinity, alignment: .trailing) }.width(min: 90, ideal: 130) - TableColumn("Installed", value: \.installedRank) { r in + TableColumn(L10n.string("Installed"), value: \.installedRank) { r in if r.isInstalled { - Image(systemName: "checkmark.circle.fill").foregroundStyle(.green).help("Installed") + Image(systemName: "checkmark.circle.fill").foregroundStyle(.green).help(L10n.string("Installed")) } }.width(min: 56, ideal: 72) } diff --git a/native/Sources/BrewBrowserKit/UpgradeSheet.swift b/native/Sources/BrewBrowserKit/UpgradeSheet.swift index ee9713c..de3174f 100644 --- a/native/Sources/BrewBrowserKit/UpgradeSheet.swift +++ b/native/Sources/BrewBrowserKit/UpgradeSheet.swift @@ -34,19 +34,19 @@ struct UpgradeSheet: View { var body: some View { VStack(alignment: .leading, spacing: 12) { - Text("Choose packages to upgrade").font(.headline) + Text(L10n.string("upgrade.choosePackages")).font(.headline) if rows.isEmpty { - Text("No outdated packages.") + Text(L10n.string("upgrade.noOutdated")) .font(.callout).foregroundStyle(.secondary) .padding(.vertical, 8) } else { // Toolbar: selected count + select-all/deselect-all toggle. HStack { - Text("\(selectedCount) of \(upgradable.count) selected") + Text(L10n.selectedCount(selectedCount, total: upgradable.count)) .font(.callout).foregroundStyle(.secondary) Spacer() - Button(allSelected ? "Deselect all" : "Select all") { + Button(allSelected ? L10n.string("action.deselectAll") : L10n.string("action.selectAll")) { if allSelected { deselectAll() } else { selectAll() } } .buttonStyle(.link) @@ -66,16 +66,14 @@ struct UpgradeSheet: View { HStack { Spacer() - Button("Cancel") { onClose() } + Button(L10n.string("action.cancel")) { onClose() } Button { let names = rows.filter { isChecked($0) }.map(\.name) onClose() Task { await model.upgradeMany(names, greedy: LocalPrefs.shared.greedyUpgrade) } } label: { Label( - selectedCount == 0 - ? "Upgrade" - : "Upgrade \(selectedCount) \(selectedCount == 1 ? "package" : "packages")", + L10n.upgradePackageButton(selectedCount), systemImage: "arrow.up.circle" ) } @@ -100,12 +98,12 @@ struct UpgradeSheet: View { Text(p.name) .frame(maxWidth: .infinity, alignment: .leading) if p.pinned { - Text("pinned") + Text(L10n.string("package.pinned")) .font(.caption2.weight(.medium)) .padding(.horizontal, 7).padding(.vertical, 2) .background(Color.orange.opacity(0.18), in: .capsule) .foregroundStyle(.orange) - .help("Pinned packages are not upgraded by brew. Unpin with `brew unpin `.") + .help(L10n.string("upgrade.pinned.help")) } KindPill(kind: p.kind) .frame(width: 84, alignment: .leading) diff --git a/native/Sources/BrewBrowserKit/VulnsService.swift b/native/Sources/BrewBrowserKit/VulnsService.swift index 3909c34..0b57017 100644 --- a/native/Sources/BrewBrowserKit/VulnsService.swift +++ b/native/Sources/BrewBrowserKit/VulnsService.swift @@ -154,16 +154,34 @@ enum VulnsServiceError: Error, LocalizedError { var errorDescription: String? { switch self { case .brewNotFound: + if L10n.isRussian { + return "Не удалось найти исполняемый файл brew. Установлен ли Homebrew?" + } return "Couldn't find the brew executable. Is Homebrew installed?" case let .scanFailed(code, stderr): + if L10n.isRussian { + return "brew vulns завершился с кодом \(code): \(stderr)" + } return "brew vulns failed (exit \(code)): \(stderr)" case let .installFailed(code, stderr): + if L10n.isRussian { + return "brew install brew-vulns завершился с кодом \(code): \(stderr)" + } return "brew install brew-vulns failed (exit \(code)): \(stderr)" case let .decodeFailed(message): + if L10n.isRussian { + return "Не удалось разобрать вывод brew vulns: \(message)" + } return "Couldn't parse brew vulns output: \(message)" case let .invalidFormulaName(name): + if L10n.isRussian { + return "Некорректное имя формулы: \(name)" + } return "Invalid formula name: \(name)" case let .spawnFailed(message): + if L10n.isRussian { + return "Не удалось запустить brew: \(message)" + } return "Couldn't launch brew: \(message)" } } diff --git a/native/Tests/BrewBrowserKitTests/CategoriesTests.swift b/native/Tests/BrewBrowserKitTests/CategoriesTests.swift index 4da51ef..31c7961 100644 --- a/native/Tests/BrewBrowserKitTests/CategoriesTests.swift +++ b/native/Tests/BrewBrowserKitTests/CategoriesTests.swift @@ -231,3 +231,46 @@ struct CategorySubgroupTests { #expect(try catalog().subgroups(in: "does-not-exist").isEmpty) } } + +@Suite("EnrichmentLocaleOverlay") +struct EnrichmentLocaleOverlayTests { + @Test func russianOverlayAppliesToSeedPackage() throws { + let catalog = try #require(EnrichmentCatalog.loadBundled(locale: "ru-RU")) + let entry = try #require(catalog.entry(for: "wget")) + #expect(entry.summary?.contains("загруз") == true) + #expect(entry.searchTerms.contains("скачать")) + // Stable package-token suggestions remain identifiers, not translations. + #expect(entry.similar.allSatisfy { !$0.contains(" ") }) + } + + @Test func englishBundleDoesNotUseRussianOverlay() throws { + let catalog = try #require(EnrichmentCatalog.loadBundled(locale: "en")) + let entry = try #require(catalog.entry(for: "wget")) + #expect(entry.summary?.contains("загруз") != true) + #expect(!entry.searchTerms.contains("скачать")) + } + + @Test func localizedMergeIgnoresOverlayStableFields() { + let base = EnrichmentEntry( + friendlyName: "Wget", + summary: "Download files.", + useCases: ["Mirror a site"], + similar: ["curl"], + tags: ["network"], + searchTerms: ["download"] + ) + let patch = EnrichmentEntry( + friendlyName: nil, + summary: "Загружает файлы.", + useCases: [], + similar: ["локализованный токен"], + tags: ["локализованный тег"], + searchTerms: ["скачать"] + ) + let merged = patch.localized(over: base) + #expect(merged.summary == "Загружает файлы.") + #expect(merged.similar == ["curl"]) + #expect(merged.tags == ["network"]) + #expect(merged.searchTerms == ["скачать"]) + } +} diff --git a/native/Tests/BrewBrowserKitTests/RecentChangesTests.swift b/native/Tests/BrewBrowserKitTests/RecentChangesTests.swift index 93acd56..5d41109 100644 --- a/native/Tests/BrewBrowserKitTests/RecentChangesTests.swift +++ b/native/Tests/BrewBrowserKitTests/RecentChangesTests.swift @@ -38,10 +38,14 @@ struct RecentChangesTests { } @Test @MainActor func failureNoticeTitlesUseActionVerb() { - #expect(AppModel.failureNoticeTitle(for: "Upgrading docker-desktop") == "Upgrade failed") - #expect(AppModel.failureNoticeTitle(for: "Installing wget") == "Install failed") - #expect(AppModel.failureNoticeTitle(for: "Uninstalling wget") == "Uninstall failed") - #expect(AppModel.failureNoticeTitle(for: "Dumping Brewfile: nightly") == "Dumping Brewfile: nightly failed") + let expectedUpgrade = L10n.isRussian ? "Не удалось обновить" : "Upgrade failed" + let expectedInstall = L10n.isRussian ? "Не удалось установить" : "Install failed" + let expectedUninstall = L10n.isRussian ? "Не удалось удалить" : "Uninstall failed" + let expectedBrewfile = L10n.isRussian ? "Не удалось выполнить: Создаём Brewfile: nightly" : "Dumping Brewfile: nightly failed" + #expect(AppModel.failureNoticeTitle(for: "Upgrading docker-desktop") == expectedUpgrade) + #expect(AppModel.failureNoticeTitle(for: "Installing wget") == expectedInstall) + #expect(AppModel.failureNoticeTitle(for: "Uninstalling wget") == expectedUninstall) + #expect(AppModel.failureNoticeTitle(for: "Dumping Brewfile: nightly") == expectedBrewfile) } // MARK: - classify() diff --git a/native/build-app.sh b/native/build-app.sh index f757580..2c1ed38 100755 --- a/native/build-app.sh +++ b/native/build-app.sh @@ -112,6 +112,12 @@ cat > "$APP/Contents/Info.plist" <<'PLIST' CFBundlePackageTypeAPPL CFBundleShortVersionString0.2.0 CFBundleVersion2 + CFBundleDevelopmentRegionen + CFBundleLocalizations + + en + ru + CFBundleIconFileAppIcon CFBundleIconNameAppIcon LSMinimumSystemVersion26.0 diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 367a6ce..d0c5d76 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -14,7 +14,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -tauri = { version = "2", features = [] } +tauri = { version = "2", features = ["macos-private-api"] } tauri-plugin-opener = "2" tauri-plugin-dialog = "2" # Issue #17 — persist window size + position across launches. Registered in diff --git a/src-tauri/data/enrichment.ru.json.gz b/src-tauri/data/enrichment.ru.json.gz new file mode 100644 index 0000000..f108d9c Binary files /dev/null and b/src-tauri/data/enrichment.ru.json.gz differ diff --git a/src-tauri/src/commands/actions.rs b/src-tauri/src/commands/actions.rs index 8a466c0..7353154 100644 --- a/src-tauri/src/commands/actions.rs +++ b/src-tauri/src/commands/actions.rs @@ -35,7 +35,11 @@ fn kind_flag(kind: PackageKind) -> &'static str { /// with "It seems there is already an App at…" — the in-app fix for #13/#102. /// `--force` overwrites instead of adopting (the heavier hammer). fn install_args(name: &str, kind: PackageKind, force: bool, adopt: bool) -> Vec { - let mut args = vec!["install".to_string(), kind_flag(kind).to_string(), name.to_string()]; + let mut args = vec![ + "install".to_string(), + kind_flag(kind).to_string(), + name.to_string(), + ]; if adopt && matches!(kind, PackageKind::Cask) { args.push("--adopt".to_string()); } @@ -49,8 +53,17 @@ fn install_args(name: &str, kind: PackageKind, force: bool, adopt: bool) -> Vec< /// cask-only (removes leftover files). `--ignore-dependencies` forces removal /// even when another installed package still requires it — the in-app escape /// for "Refusing to uninstall … because it is required by…" (#100). -fn uninstall_args(name: &str, kind: PackageKind, zap: bool, ignore_dependencies: bool) -> Vec { - let mut args = vec!["uninstall".to_string(), kind_flag(kind).to_string(), name.to_string()]; +fn uninstall_args( + name: &str, + kind: PackageKind, + zap: bool, + ignore_dependencies: bool, +) -> Vec { + let mut args = vec![ + "uninstall".to_string(), + kind_flag(kind).to_string(), + name.to_string(), + ]; if zap && matches!(kind, PackageKind::Cask) { args.push("--zap".to_string()); } @@ -365,7 +378,12 @@ mod tests { fn uninstall_ignore_dependencies_force_remove() { assert_eq!( uninstall_args("gstreamer-runtime", PackageKind::Cask, false, true), - svec(&["uninstall", "--cask", "gstreamer-runtime", "--ignore-dependencies"]) + svec(&[ + "uninstall", + "--cask", + "gstreamer-runtime", + "--ignore-dependencies" + ]) ); } diff --git a/src-tauri/src/commands/disk_usage.rs b/src-tauri/src/commands/disk_usage.rs index 4804cef..8944447 100644 --- a/src-tauri/src/commands/disk_usage.rs +++ b/src-tauri/src/commands/disk_usage.rs @@ -482,7 +482,10 @@ mod tests { assert_eq!(parse_size_token("1KB"), Some(1024)); assert_eq!(parse_size_token("1.5KB"), Some(1536)); assert_eq!(parse_size_token("500MB"), Some(500 * 1024 * 1024)); - assert_eq!(parse_size_token("1.2GB"), Some((1.2 * 1024.0 * 1024.0 * 1024.0) as u64)); + assert_eq!( + parse_size_token("1.2GB"), + Some((1.2 * 1024.0 * 1024.0 * 1024.0) as u64) + ); // trailing punctuation from a sentence is tolerated assert_eq!(parse_size_token("2GB."), Some(2 * 1024 * 1024 * 1024)); } diff --git a/src-tauri/src/commands/enrichment.rs b/src-tauri/src/commands/enrichment.rs index bae250a..acfd6cb 100644 --- a/src-tauri/src/commands/enrichment.rs +++ b/src-tauri/src/commands/enrichment.rs @@ -14,14 +14,39 @@ use std::sync::Arc; use tauri::State; use crate::commands::info::validate_package_name; -use crate::enrichment::{EnrichmentData, EnrichmentEntry}; +use crate::enrichment::{normalize_locale, EnrichmentData, EnrichmentEntry}; use crate::error::BrewError; use crate::state::AppState; /// Return the full enrichment payload. Memoised on `AppState` so /// subsequent calls are an Arc-clone, not a re-parse. #[tauri::command] -pub async fn enrichment_data(state: State<'_, AppState>) -> Result, BrewError> { +pub async fn enrichment_data( + locale: Option, + state: State<'_, AppState>, +) -> Result, BrewError> { + let locale = normalize_locale(locale.as_deref()); + if locale != "en" { + { + let cached = state.enrichment_locale_cache.lock().await; + if let Some(data) = cached.get(locale) { + return Ok(Arc::clone(data)); + } + } + + let base = enrichment_base_data(state.clone()).await?; + let localized = Arc::new(EnrichmentData::localized(&base, Some(locale))?); + let mut cached = state.enrichment_locale_cache.lock().await; + cached.insert(locale.to_string(), Arc::clone(&localized)); + return Ok(localized); + } + + enrichment_base_data(state).await +} + +async fn enrichment_base_data( + state: State<'_, AppState>, +) -> Result, BrewError> { { let cached = state.enrichment_cache.lock().await; if let Some(data) = cached.as_ref() { @@ -41,10 +66,11 @@ pub async fn enrichment_data(state: State<'_, AppState>) -> Result, state: State<'_, AppState>, ) -> Result, BrewError> { validate_package_name(&name)?; - let data = enrichment_data(state).await?; + let data = enrichment_data(locale, state).await?; Ok(data.entries.get(&name).cloned()) } diff --git a/src-tauri/src/commands/search.rs b/src-tauri/src/commands/search.rs index 1370530..63873b5 100644 --- a/src-tauri/src/commands/search.rs +++ b/src-tauri/src/commands/search.rs @@ -213,17 +213,55 @@ mod weight { pub const TAG: u32 = 100; } +fn localized_category_labels(label: &str, locale: &str) -> Vec { + let mut labels = vec![label.to_string()]; + if locale != "ru" { + return labels; + } + let ru = match label { + "AI & ML" => Some("ИИ и ML"), + "Browsers" => Some("Браузеры"), + "Cloud & DevOps" => Some("Облако и DevOps"), + "Communication" => Some("Коммуникации"), + "Data" => Some("Данные"), + "Developer Tools" => Some("Инструменты разработчика"), + "Editors & IDEs" => Some("Редакторы и IDE"), + "Education" => Some("Образование"), + "Games & Entertainment" => Some("Игры и развлечения"), + "Graphics & Design" => Some("Графика и дизайн"), + "Music" => Some("Музыка"), + "Office & Docs" => Some("Офис и документы"), + "Productivity" => Some("Продуктивность"), + "Security" => Some("Безопасность"), + "System Utilities" => Some("Системные утилиты"), + "Terminal" => Some("Терминал"), + "Video & Audio" => Some("Видео и аудио"), + "Writing" => Some("Работа с текстом"), + "Uncategorized" => Some("Без категории"), + "Other" => Some("Другое"), + _ => None, + }; + if let Some(ru) = ru { + if ru != label { + labels.push(ru.to_string()); + } + } + labels +} + #[tauri::command] pub async fn local_search( query: String, + locale: Option, state: State<'_, AppState>, ) -> Result { validate_search_query(&query)?; + let locale = crate::enrichment::normalize_locale(locale.as_deref()); // Parse terms: split on whitespace, lowercase, dedupe-by-set, drop empty. let mut terms: Vec = query .split_whitespace() - .map(|t| t.to_ascii_lowercase()) + .map(|t| t.to_lowercase()) .filter(|t| !t.is_empty()) .collect(); terms.sort(); @@ -244,10 +282,13 @@ pub async fn local_search( let guard = state.catalog.read().await; std::sync::Arc::clone(&*guard) }; - let enrichment = match crate::commands::enrichment::enrichment_data(state.clone()).await { - Ok(arc) => Some(arc), - Err(_) => None, // best-effort; if enrichment fails we just lose AI fields - }; + let enrichment = + match crate::commands::enrichment::enrichment_data(Some(locale.to_string()), state.clone()) + .await + { + Ok(arc) => Some(arc), + Err(_) => None, // best-effort; if enrichment fails we just lose AI fields + }; let categories = match crate::commands::categories::categories_data(state.clone()).await { Ok(arc) => Some(arc), Err(_) => None, @@ -257,24 +298,34 @@ pub async fn local_search( // Build a token → Vec map once so per-row scoring is // a cheap lookup instead of an O(categories) scan per token. - let mut formula_labels: std::collections::HashMap<&str, Vec<&str>> = + let mut formula_labels: std::collections::HashMap<&str, Vec> = std::collections::HashMap::new(); - let mut cask_labels: std::collections::HashMap<&str, Vec<&str>> = + let mut cask_labels: std::collections::HashMap<&str, Vec> = std::collections::HashMap::new(); if let Some(cat) = categories.as_deref() { for (token, slugs) in cat.formulae.iter() { - let labels: Vec<&str> = slugs + let labels: Vec = slugs .iter() - .filter_map(|s| cat.categories.get(s).map(|m| m.label.as_str())) + .filter_map(|s| { + cat.categories + .get(s) + .map(|m| localized_category_labels(&m.label, locale)) + }) + .flatten() .collect(); if !labels.is_empty() { formula_labels.insert(token.as_str(), labels); } } for (token, slugs) in cat.casks.iter() { - let labels: Vec<&str> = slugs + let labels: Vec = slugs .iter() - .filter_map(|s| cat.categories.get(s).map(|m| m.label.as_str())) + .filter_map(|s| { + cat.categories + .get(s) + .map(|m| localized_category_labels(&m.label, locale)) + }) + .flatten() .collect(); if !labels.is_empty() { cask_labels.insert(token.as_str(), labels); @@ -288,21 +339,26 @@ pub async fn local_search( // preferred over upstream desc) so the frontend can show it without // a second IPC round-trip. let score_pkg = - |name: &str, desc: Option<&str>, labels: &[&str]| -> Option<(u32, Option)> { + |name: &str, desc: Option<&str>, labels: &[String]| -> Option<(u32, Option)> { // AI summary + friendly name from enrichment, if available. let entry = enrichment.as_deref().and_then(|e| e.entries.get(name)); let friendly = entry.and_then(|e| e.friendly_name.as_deref()); let summary = entry.and_then(|e| e.summary.as_deref()); + let search_terms: Vec<&str> = entry + .map(|e| e.search_terms.iter().map(|s| s.as_str()).collect()) + .unwrap_or_default(); let tags: Vec<&str> = entry .map(|e| e.tags.iter().map(|s| s.as_str()).collect()) .unwrap_or_default(); - let name_lc = name.to_ascii_lowercase(); - let desc_lc = desc.map(|s| s.to_ascii_lowercase()); - let friendly_lc = friendly.map(|s| s.to_ascii_lowercase()); - let summary_lc = summary.map(|s| s.to_ascii_lowercase()); - let labels_lc: Vec = labels.iter().map(|s| s.to_ascii_lowercase()).collect(); - let tags_lc: Vec = tags.iter().map(|s| s.to_ascii_lowercase()).collect(); + let name_lc = name.to_lowercase(); + let desc_lc = desc.map(|s| s.to_lowercase()); + let friendly_lc = friendly.map(|s| s.to_lowercase()); + let summary_lc = summary.map(|s| s.to_lowercase()); + let labels_lc: Vec = labels.iter().map(|s| s.to_lowercase()).collect(); + let search_terms_lc: Vec = + search_terms.iter().map(|s| s.to_lowercase()).collect(); + let tags_lc: Vec = tags.iter().map(|s| s.to_lowercase()).collect(); let mut total: u32 = 0; for term in &terms { @@ -334,6 +390,14 @@ pub async fn local_search( best = best.max(weight::SUMMARY); } } + // locale search aliases — roughly summary-strength, but + // intentionally separate from visible tags. + for alias in &search_terms_lc { + if alias.contains(term) { + best = best.max(weight::SUMMARY); + break; + } + } // desc substring. if let Some(dl) = &desc_lc { if dl.contains(term) { @@ -480,7 +544,7 @@ fn _force_link_validate_package_name() { #[cfg(test)] mod tests { - use super::{is_brew_search_no_match, validate_search_query}; + use super::{is_brew_search_no_match, localized_category_labels, validate_search_query}; use crate::error::BrewError; // ---------- is_brew_search_no_match ---------- @@ -609,4 +673,16 @@ mod tests { fn allows_internal_dash_after_letter() { validate_search_query("a-b").expect("internal dash is fine"); } + + #[test] + fn localized_category_labels_keep_english_fallback() { + assert_eq!( + localized_category_labels("Security", "ru"), + vec!["Security".to_string(), "Безопасность".to_string()] + ); + assert_eq!( + localized_category_labels("Security", "en"), + vec!["Security".to_string()] + ); + } } diff --git a/src-tauri/src/enrichment/mod.rs b/src-tauri/src/enrichment/mod.rs index 4b2700c..890937d 100644 --- a/src-tauri/src/enrichment/mod.rs +++ b/src-tauri/src/enrichment/mod.rs @@ -1,10 +1,13 @@ //! Bundled LLM-derived enrichment (Phase 13). //! -//! The enrichment file is the offline output of `tools/enrich/enrich.py` — -//! per-token friendly names, expanded summaries, use-case bullets, -//! similar-package suggestions, and tech-stack tags. It ships embedded -//! in the binary via `include_bytes!`; **there is no runtime LLM -//! request**, and there is no on-disk user-data path for v1. +//! The base enrichment file is the offline output of +//! `tools/enrich/enrich.py` — per-token friendly names, expanded +//! summaries, use-case bullets, similar-package suggestions, and +//! tech-stack tags. Locale-specific overlays, when present, are small +//! partial files keyed by the same Homebrew tokens; they replace only +//! user-facing prose and add locale search terms. Everything ships +//! embedded in the binary via `include_bytes!`; **there is no runtime +//! LLM request**, and there is no on-disk user-data path for v1. //! //! ## Security caps (mirrors the Phase 12a catalog pattern) //! @@ -69,6 +72,14 @@ pub const MAX_TAGS_COUNT: usize = 12; /// Per-tag cap. Tags are normalised to `[a-z0-9-]`; we cap length at 30. pub const MAX_TAG_LEN: usize = 30; +/// Per localized search term cap. These are not displayed as tags; they +/// exist only so locale-aware search can match natural-language queries +/// without changing package IDs or upstream Homebrew descriptions. +pub const MAX_SEARCH_TERM_LEN: usize = 60; + +/// Max localized search terms per package. +pub const MAX_SEARCH_TERMS_COUNT: usize = 16; + // ---------- Bundled artifact ---------- /// Bundled gzipped enrichment payload (produced by `tools/enrich/enrich.py`). @@ -76,6 +87,11 @@ pub const MAX_TAG_LEN: usize = 30; /// run yet — the bundle still works, the UI just renders no enriched data. const ENRICHMENT_JSON_GZ: &[u8] = include_bytes!("../../data/enrichment.json.gz"); +/// Partial Russian overlay. Future locales should follow the same +/// `enrichment..json.gz` schema and be routed through +/// [`localized_overlay_bytes`]. +const ENRICHMENT_RU_JSON_GZ: &[u8] = include_bytes!("../../data/enrichment.ru.json.gz"); + // ---------- Types ---------- /// One enrichment record, keyed by Homebrew token. All fields are @@ -105,6 +121,12 @@ pub struct EnrichmentEntry { /// Tech-stack tags (3-8, capped at 12). Lowercase + hyphenated. pub tags: Vec, + + /// Locale-specific search terms. Not shown in the UI; used by + /// `local_search` so translated summaries and domain words can be + /// searched alongside stable package names and English fallbacks. + #[serde(alias = "search_terms", default, skip_serializing_if = "Vec::is_empty")] + pub search_terms: Vec, } /// Full enrichment payload, parsed once from the bundled `.gz` and @@ -122,6 +144,21 @@ pub struct EnrichmentData { pub entries: HashMap, } +/// Locale-specific partial overlay. This intentionally does not replace +/// the base bundle: it is merged over the base for one locale, field by +/// field, with English fallback preserved for missing translated fields. +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase", default)] +pub struct EnrichmentLocaleOverlay { + pub locale: String, + pub version: String, + #[serde(alias = "generated_at")] + pub generated_at: String, + #[serde(alias = "base_version", skip_serializing_if = "Option::is_none")] + pub base_version: Option, + pub entries: HashMap, +} + impl EnrichmentData { /// Decode + parse + validate the bundled enrichment payload. Returns /// the placeholder shape ([`EnrichmentData::empty`]) when the bundle @@ -155,6 +192,25 @@ impl EnrichmentData { Ok(Arc::new(data)) } + /// Return a localized copy of a parsed base bundle. Unknown locales + /// intentionally return the base data unchanged, which makes adding a + /// locale opt-in and keeps package identifiers/search fallbacks stable. + pub fn localized( + base: &EnrichmentData, + locale: Option<&str>, + ) -> Result { + let normalized = normalize_locale(locale); + if normalized == "en" { + return Ok(base.clone()); + } + + let Some(bytes) = localized_overlay_bytes(normalized) else { + return Ok(base.clone()); + }; + let overlay = load_locale_overlay(bytes, normalized)?; + Ok(apply_locale_overlay(base, &overlay)) + } + /// Construct an empty enrichment payload — used by callers that want /// to short-circuit when enrichment is disabled / unavailable. Same /// shape the placeholder bundle deserializes to. @@ -175,6 +231,19 @@ impl EnrichmentData { } } +/// Normalize a BCP-47-ish UI locale to the enrichment overlay bucket we +/// support today. This keeps `ru`, `ru-RU`, and future regional Russian +/// variants pointed at the same compact overlay. +pub fn normalize_locale(locale: Option<&str>) -> &'static str { + let Some(locale) = locale else { return "en" }; + let lc = locale.trim().to_ascii_lowercase(); + if lc == "ru" || lc.starts_with("ru-") || lc.starts_with("ru_") { + "ru" + } else { + "en" + } +} + // ---------- Helpers ---------- /// Decompress gzipped `bytes` with a cap of [`MAX_DECOMPRESSED_BYTES`] @@ -202,38 +271,130 @@ fn decompress_capped(bytes: &[u8], context: &str) -> Result, BrewError> /// rest of the bundle. fn validate_and_truncate(data: &mut EnrichmentData) { for entry in data.entries.values_mut() { - if let Some(name) = entry.friendly_name.as_mut() { - truncate_utf8(name, MAX_FRIENDLY_NAME_LEN); + validate_and_truncate_entry(entry); + } +} + +fn validate_and_truncate_entry(entry: &mut EnrichmentEntry) { + if let Some(name) = entry.friendly_name.as_mut() { + truncate_utf8(name, MAX_FRIENDLY_NAME_LEN); + } + if let Some(sum) = entry.summary.as_mut() { + truncate_utf8(sum, MAX_SUMMARY_LEN); + } + // Truncate count first, then each entry's length. + entry.use_cases.truncate(MAX_USE_CASES_COUNT); + for uc in entry.use_cases.iter_mut() { + truncate_utf8(uc, MAX_USE_CASE_LEN); + } + // `similar` — drop any token that fails the package-name validator + // (defense against LLM hallucination + a future swapped bundle + // smuggling bogus tokens). + entry.similar.retain(|t| validate_package_name(t).is_ok()); + entry.similar.truncate(MAX_SIMILAR_COUNT); + + entry.tags.truncate(MAX_TAGS_COUNT); + for t in entry.tags.iter_mut() { + truncate_utf8(t, MAX_TAG_LEN); + // Defense-in-depth: tags must be `[a-z0-9-]`. Drop the entry if + // it isn't — empty result is fine, the field just disappears + // from the UI. + if !t + .bytes() + .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-') + { + t.clear(); + } + } + entry.tags.retain(|t| !t.is_empty()); + + entry.search_terms.truncate(MAX_SEARCH_TERMS_COUNT); + for term in entry.search_terms.iter_mut() { + truncate_utf8(term, MAX_SEARCH_TERM_LEN); + } + entry.search_terms.retain(|term| !term.trim().is_empty()); +} + +fn localized_overlay_bytes(locale: &str) -> Option<&'static [u8]> { + match locale { + "ru" => Some(ENRICHMENT_RU_JSON_GZ), + _ => None, + } +} + +fn load_locale_overlay(bytes: &[u8], locale: &str) -> Result { + if bytes.len() > MAX_RAW_BYTES { + return Err(BrewError::Io { + message: format!( + "bundled enrichment.{locale}.json.gz is {} bytes, exceeds {} byte cap", + bytes.len(), + MAX_RAW_BYTES + ), + }); + } + + let decoded = decompress_capped(bytes, &format!("bundled enrichment.{locale}.json.gz"))?; + let mut overlay: EnrichmentLocaleOverlay = + serde_json::from_slice(&decoded).map_err(|e| BrewError::Internal { + message: format!("enrichment.{locale}.json.gz parse failed: {e}"), + })?; + let declared = normalize_locale(Some(&overlay.locale)); + if !overlay.locale.trim().is_empty() && declared != locale { + return Err(BrewError::Internal { + message: format!( + "enrichment.{locale}.json.gz declares locale {:?}, expected {locale}", + overlay.locale + ), + }); + } + + for entry in overlay.entries.values_mut() { + validate_and_truncate_entry(entry); + } + Ok(overlay) +} + +fn apply_locale_overlay( + base: &EnrichmentData, + overlay: &EnrichmentLocaleOverlay, +) -> EnrichmentData { + let mut localized = base.clone(); + localized.version = if overlay.version.is_empty() { + base.version.clone() + } else { + format!("{}+{}", base.version, overlay.version) + }; + localized.generated_at = if overlay.generated_at.is_empty() { + base.generated_at.clone() + } else { + overlay.generated_at.clone() + }; + + for (token, patch) in &overlay.entries { + let entry = localized.entries.entry(token.clone()).or_default(); + if let Some(value) = patch + .friendly_name + .as_ref() + .filter(|v| !v.trim().is_empty()) + { + entry.friendly_name = Some(value.clone()); } - if let Some(sum) = entry.summary.as_mut() { - truncate_utf8(sum, MAX_SUMMARY_LEN); + if let Some(value) = patch.summary.as_ref().filter(|v| !v.trim().is_empty()) { + entry.summary = Some(value.clone()); } - // Truncate count first, then each entry's length. - entry.use_cases.truncate(MAX_USE_CASES_COUNT); - for uc in entry.use_cases.iter_mut() { - truncate_utf8(uc, MAX_USE_CASE_LEN); + if !patch.use_cases.is_empty() { + entry.use_cases = patch.use_cases.clone(); } - // `similar` — drop any token that fails the package-name - // validator (defense against LLM hallucination + a future - // swapped bundle smuggling bogus tokens). - entry.similar.retain(|t| validate_package_name(t).is_ok()); - entry.similar.truncate(MAX_SIMILAR_COUNT); - - entry.tags.truncate(MAX_TAGS_COUNT); - for t in entry.tags.iter_mut() { - truncate_utf8(t, MAX_TAG_LEN); - // Defense-in-depth: tags must be `[a-z0-9-]`. Drop the - // entry if it isn't — empty result is fine, the field just - // disappears from the UI. - if !t - .bytes() - .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-') - { - t.clear(); - } + if !patch.search_terms.is_empty() { + entry.search_terms = patch.search_terms.clone(); } - entry.tags.retain(|t| !t.is_empty()); + // `similar` and `tags` are intentionally not localized here: + // `similar` carries stable Homebrew tokens and `tags` are compact + // technical labels. Locale search vocabulary belongs in + // `search_terms`. } + + localized } /// In-place UTF-8-safe truncate. Walks back to the previous char @@ -392,6 +553,105 @@ mod tests { } } + #[test] + fn validate_caps_search_terms_but_keeps_unicode() { + let mut data = EnrichmentData::empty(); + let too_many: Vec = (0..30) + .map(|i| { + if i == 0 { + "русский поисковый термин".to_string() + } else { + "x".repeat(MAX_SEARCH_TERM_LEN + 20) + } + }) + .collect(); + data.entries.insert( + "localized".into(), + EnrichmentEntry { + search_terms: too_many, + ..Default::default() + }, + ); + validate_and_truncate(&mut data); + let terms = &data.entries["localized"].search_terms; + assert_eq!(terms.len(), MAX_SEARCH_TERMS_COUNT); + assert_eq!(terms[0], "русский поисковый термин"); + for term in terms { + assert!(term.len() <= MAX_SEARCH_TERM_LEN); + } + } + + #[test] + fn locale_overlay_replaces_prose_and_preserves_stable_fields() { + let mut base = EnrichmentData::empty(); + base.version = "base".into(); + base.generated_at = "2026-01-01T00:00:00Z".into(); + base.entries.insert( + "wget".into(), + EnrichmentEntry { + friendly_name: Some("Wget".into()), + summary: Some("Download files from the web.".into()), + use_cases: vec!["Mirror a website".into()], + similar: vec!["curl".into()], + tags: vec!["network".into()], + search_terms: vec!["download".into()], + }, + ); + let mut overlay = EnrichmentLocaleOverlay { + locale: "ru".into(), + version: "ru-1".into(), + generated_at: "2026-01-02T00:00:00Z".into(), + ..Default::default() + }; + overlay.entries.insert( + "wget".into(), + EnrichmentEntry { + summary: Some("Загружает файлы по HTTP, HTTPS и FTP.".into()), + use_cases: vec!["Скачать архив из скрипта".into()], + search_terms: vec!["скачать".into(), "загрузка".into()], + similar: vec!["not-localized".into()], + tags: vec!["не-тег".into()], + ..Default::default() + }, + ); + + let localized = apply_locale_overlay(&base, &overlay); + let entry = &localized.entries["wget"]; + assert_eq!(localized.version, "base+ru-1"); + assert_eq!(localized.generated_at, "2026-01-02T00:00:00Z"); + assert_eq!(entry.friendly_name.as_deref(), Some("Wget")); + assert_eq!( + entry.summary.as_deref(), + Some("Загружает файлы по HTTP, HTTPS и FTP.") + ); + assert_eq!(entry.use_cases, vec!["Скачать архив из скрипта"]); + assert_eq!(entry.search_terms, vec!["скачать", "загрузка"]); + assert_eq!(entry.similar, vec!["curl"]); + assert_eq!(entry.tags, vec!["network"]); + } + + #[test] + fn bundled_ru_overlay_applies_to_seed_entry() { + let base = EnrichmentData::load().expect("load base bundle"); + let localized = EnrichmentData::localized(&base, Some("ru-RU")).expect("merge ru overlay"); + let entry = localized + .entries + .get("wget") + .expect("ru seed should include wget"); + assert!( + entry + .summary + .as_deref() + .unwrap_or_default() + .contains("загруз"), + "expected Russian wget summary" + ); + assert!( + entry.search_terms.iter().any(|term| term == "скачать"), + "expected Russian search term" + ); + } + #[test] fn decompress_capped_rejects_oversize() { // Synthesize a gzip stream that decompresses to ~ MAX_DECOMPRESSED_BYTES + 1. @@ -435,6 +695,7 @@ mod tests { use_cases: vec!["Run a local db".into()], similar: vec!["mariadb".into()], tags: vec!["database".into(), "sql".into()], + search_terms: vec!["postgres".into()], }; let json = serde_json::to_string(&e).expect("serialize"); // Camel-case rename rules. @@ -450,11 +711,13 @@ mod tests { "summary": "Database.", "use_cases": ["Run a local db"], "similar": ["mariadb"], - "tags": ["database", "sql"] + "tags": ["database", "sql"], + "search_terms": ["postgres"] }"#; let from_snake: EnrichmentEntry = serde_json::from_str(snake).expect("snake parse"); assert_eq!(from_snake.friendly_name.as_deref(), Some("PostgreSQL 14")); assert_eq!(from_snake.use_cases, vec!["Run a local db".to_string()]); + assert_eq!(from_snake.search_terms, vec!["postgres".to_string()]); // Erase to remove a field and verify Option re-serialization // skips when None. diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs index e1cbe86..c3d32aa 100644 --- a/src-tauri/src/state.rs +++ b/src-tauri/src/state.rs @@ -114,6 +114,11 @@ pub struct AppState { /// this is purely a decode+parse cache. pub enrichment_cache: Arc>>>, + /// Locale-specific enrichment copies, memoised after merging a small + /// `enrichment..json.gz` overlay over the base bundle. The + /// base cache above remains canonical and untouched. + pub enrichment_locale_cache: Arc>>>, + /// Disk-usage report cache. Filled by `disk_usage`, invalidated by /// `disk_usage_clear_cache`. TTL is checked inside the command itself /// (60 s) so concurrent callers don't double-spawn `du` on each other. @@ -228,6 +233,7 @@ impl AppState { installed_cache: RwLock::new(None), categories_cache: Arc::new(Mutex::new(None)), enrichment_cache: Arc::new(Mutex::new(None)), + enrichment_locale_cache: Arc::new(Mutex::new(HashMap::new())), disk_usage_cache: Arc::new(Mutex::new(None)), services_cache: Arc::new(Mutex::new(None)), catalog: RwLock::new(Arc::new(bundled)), diff --git a/src-tauri/src/vulns/cache.rs b/src-tauri/src/vulns/cache.rs index 10d9dc5..a04ddc7 100644 --- a/src-tauri/src/vulns/cache.rs +++ b/src-tauri/src/vulns/cache.rs @@ -287,7 +287,10 @@ impl VulnsCache { /// that cruft so the cache always mirrors the latest scan, exactly like the /// native shell's wholesale `vulnFindings = findings`. Fingerprint state is /// preserved (set separately via [`Self::record_fingerprint`]). - pub fn replace_full_scan(&mut self, records: impl IntoIterator)>) { + pub fn replace_full_scan( + &mut self, + records: impl IntoIterator)>, + ) { self.file.entries.clear(); let now = Utc::now(); for (key, vulns) in records { @@ -427,7 +430,10 @@ mod tests { // cache, not accumulate. Prior scans left stale records. let mut c = VulnsCache::new_empty(); c.put(key("augeas", "1.14.0"), vec![vuln("CVE-OLD")]); // old version - c.put(key("ghostscript", "10.0"), vec![vuln("CVE-GONE-1"), vuln("CVE-GONE-2")]); // no longer flagged + c.put( + key("ghostscript", "10.0"), + vec![vuln("CVE-GONE-1"), vuln("CVE-GONE-2")], + ); // no longer flagged assert_eq!(c.file.entries.len(), 2); // Fresh full scan: only augeas@1.14.1 with two findings. @@ -435,10 +441,19 @@ mod tests { assert_eq!(c.file.entries.len(), 1, "stale entries must be cleared"); assert!(c.get_any(&key("augeas", "1.14.1")).is_some()); - assert!(c.get_any(&key("augeas", "1.14.0")).is_none(), "old version dropped"); - assert!(c.get_any(&key("ghostscript", "10.0")).is_none(), "no-longer-flagged dropped"); + assert!( + c.get_any(&key("augeas", "1.14.0")).is_none(), + "old version dropped" + ); + assert!( + c.get_any(&key("ghostscript", "10.0")).is_none(), + "no-longer-flagged dropped" + ); let total: usize = c.file.entries.values().map(|r| r.vulns.len()).sum(); - assert_eq!(total, 2, "finding total reflects the scan, not the accumulation"); + assert_eq!( + total, 2, + "finding total reflects the scan, not the accumulation" + ); assert!(c.dirty); } diff --git a/src-tauri/src/vulns/client.rs b/src-tauri/src/vulns/client.rs index 8f69a8a..d8f21af 100644 --- a/src-tauri/src/vulns/client.rs +++ b/src-tauri/src/vulns/client.rs @@ -309,9 +309,12 @@ pub async fn scan_all(brew: &Path) -> Result, BrewError> { // on stdout and break JSON parsing (parity with the native scanner, and // belt-and-suspenders for issue #62). parse_scan_output also salvages JSON // from any residual noise. - let raw = - run_vulns_capture(brew, &["vulns", "--quiet", "--json"], "brew vulns --quiet --json") - .await?; + let raw = run_vulns_capture( + brew, + &["vulns", "--quiet", "--json"], + "brew vulns --quiet --json", + ) + .await?; parse_scan_output(&raw, "brew vulns --quiet --json") } @@ -463,7 +466,9 @@ pub fn validate_formula_name(name: &str) -> Result<(), BrewError> { // `a//b`, `.`/`..` path segments, or deeper paths. let segments: Vec<&str> = name.split('/').collect(); let well_formed = matches!(segments.len(), 1 | 3) - && segments.iter().all(|s| !s.is_empty() && *s != "." && *s != ".."); + && segments + .iter() + .all(|s| !s.is_empty() && *s != "." && *s != ".."); if !well_formed { return Err(BrewError::InvalidArgument { message: format!("invalid character(s) in formula name: {name}"), diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index c750bb9..223d999 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -11,6 +11,7 @@ }, "app": { "withGlobalTauri": false, + "macOSPrivateApi": true, "windows": [ { "title": "brew-browser", diff --git a/src/lib/api.ts b/src/lib/api.ts index 0eea135..1970d98 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -143,8 +143,8 @@ export function brewSearchDesc(query: string): Promise { * Multi-term queries AND across fields. Cap is 200 hits split between * formulae and casks. */ -export function localSearch(query: string): Promise { - return invoke("local_search", { query }); +export function localSearch(query: string, locale?: string): Promise { + return invoke("local_search", { query, locale }); } // ============================================================ @@ -486,8 +486,8 @@ export function categoriesData(): Promise { * Features toggle gate; components should reach for the store, not * this raw wrapper. */ -export function enrichmentData(): Promise { - return invoke("enrichment_data"); +export function enrichmentData(locale?: string): Promise { + return invoke("enrichment_data", { locale }); } /** @@ -496,8 +496,8 @@ export function enrichmentData(): Promise { * etc.). The backend validates the token against `validate_package_name` * first so an IPC caller can't probe with shell metacharacters. */ -export function enrichmentLookup(name: string): Promise { - return invoke("enrichment_lookup", { name }); +export function enrichmentLookup(name: string, locale?: string): Promise { + return invoke("enrichment_lookup", { name, locale }); } // ============================================================ diff --git a/src/lib/components/AboutModal.svelte b/src/lib/components/AboutModal.svelte index 5500cc0..aea6254 100644 --- a/src/lib/components/AboutModal.svelte +++ b/src/lib/components/AboutModal.svelte @@ -11,6 +11,7 @@ import { safeOpenUrl } from "$lib/util/url"; import { isLinux } from "$lib/util/platform"; import { SPONSOR_URL } from "$lib/util/donate"; + import { t } from "$lib/i18n/messages"; const REPO_URL = "https://github.com/msitarzewski/brew-browser"; const LICENSE_URL = "https://github.com/msitarzewski/brew-browser/blob/main/LICENSE"; @@ -61,50 +62,47 @@ aria-labelledby="about-title" data-tauri-drag-region="false" > -

brew-browser

-

A native macOS GUI for Homebrew.

+

{t("about.tagline", ui.locale)}

-
Version
{appVer ?? "…"}
+
{t("Version", ui.locale)}
{appVer ?? "…"}
Homebrew
{env.report?.version ?? "—"}
-
License
-
Repo
+
{t("License", ui.locale)}
+
{t("Repo", ui.locale)}
-
-

Built with

+

{t("Built with", ui.locale)}

- — the multi-agent toolkit that orchestrated the waves (Backend - Architect, Frontend Developer, Security Engineer, Code Reviewer, - Technical Writer, and friends), powered by Claude Code in - the terminal, running Opus 4.7 [1m]. + {t("about.creditsBody.beforeClaude", ui.locale)} + Claude Code + {t("about.creditsBody.afterClaude", ui.locale)} + Opus 4.7 [1m].

- Thanks also to the Homebrew project for the package data, every - {isLinux ? "formula" : "formula and cask"} maintainer for their work, - and Tauri for the native shell. + {isLinux ? t("about.thanksLinux", ui.locale) : t("about.thanksMac", ui.locale)}

- Zero telemetry. Zero accounts. Every outbound network call is - documented in Settings → Network. + {t("about.posture", ui.locale)}

diff --git a/src/lib/components/ActivityDrawer.svelte b/src/lib/components/ActivityDrawer.svelte index d5ef460..6ca3336 100644 --- a/src/lib/components/ActivityDrawer.svelte +++ b/src/lib/components/ActivityDrawer.svelte @@ -12,6 +12,7 @@ import { activity } from "$lib/stores/activity.svelte"; import { ui } from "$lib/stores/ui.svelte"; import { toast } from "$lib/stores/toast.svelte"; + import { t, type MessageKey } from "$lib/i18n/messages"; import type { ActivityLine } from "$lib/types"; import { openReportIssueFromJob } from "$lib/util/reportIssue"; import { @@ -51,12 +52,23 @@ activity.handleEvent(evt); }); } catch (e) { - toast.error("Couldn't run that action", String(e)); + toast.error(ui.locale === "ru" ? "Не удалось выполнить действие" : "Couldn't run that action", String(e)); } finally { recoveryBusy = false; } } + function recoveryText(source: string): string { + return t(source as MessageKey, ui.locale); + } + + function recoveryReasonText(opt: RecoveryOption): string { + if (opt.action === "install") { + return t("recovery.reason.alreadyInstalled", ui.locale, { name: opt.name }); + } + return t("recovery.reason.stillRequired", ui.locale, { name: opt.name }); + } + // ─── Adaptive aria-live for high-volume install streams (security audit §N4). // // Brew installs can dump hundreds of lines (downloads, tar progress, post- @@ -153,9 +165,9 @@ // it changes; empty string while running emits nothing. let completionAnnounce = $derived.by(() => { if (!activeJob || activeJob.status === "running") return ""; - if (activeJob.status === "succeeded") return `${activeJob.label}: done.`; - if (activeJob.status === "failed") return `${activeJob.label}: failed.`; - if (activeJob.status === "canceled") return `${activeJob.label}: canceled.`; + if (activeJob.status === "succeeded") return ui.locale === "ru" ? `${activeJob.label}: готово.` : `${activeJob.label}: done.`; + if (activeJob.status === "failed") return ui.locale === "ru" ? `${activeJob.label}: ошибка.` : `${activeJob.label}: failed.`; + if (activeJob.status === "canceled") return ui.locale === "ru" ? `${activeJob.label}: отменено.` : `${activeJob.label}: canceled.`; return ""; }); @@ -186,9 +198,9 @@ const text = activeJob.lines.map((l: ActivityLine) => l.text).join("\n"); try { await navigator.clipboard.writeText(text); - toast.success("Copied log to clipboard"); + toast.success(ui.locale === "ru" ? "Лог скопирован в буфер обмена" : "Copied log to clipboard"); } catch { - toast.error("Copy failed"); + toast.error(ui.locale === "ru" ? "Не удалось скопировать лог" : "Copy failed"); } } @@ -206,15 +218,20 @@ } /** Shorter "4.2s" / "1m 14s" form for the quiet completion footer. */ function formatElapsed(ms: number): string { - if (ms < 10_000) return `${(ms / 1000).toFixed(1)}s`; + if (ms < 10_000) return ui.locale === "ru" ? `${(ms / 1000).toFixed(1)} с` : `${(ms / 1000).toFixed(1)}s`; const totalSec = Math.round(ms / 1000); const m = Math.floor(totalSec / 60); const s = totalSec % 60; + if (ui.locale === "ru") return m > 0 ? `${m} мин ${s} с` : `${s} с`; return m > 0 ? `${m}m ${s}s` : `${s}s`; } function failedFooter(job: typeof activeJob): string { - if (!job) return "Failed."; + if (!job) return ui.locale === "ru" ? "Не удалось выполнить." : "Failed."; + if (ui.locale === "ru") { + const base = `Не удалось выполнить${job.durationMs ? ` за ${formatElapsed(job.durationMs)}` : ""}.`; + return `${base} См. уведомление справа.`; + } const base = `Failed${job.durationMs ? ` after ${formatElapsed(job.durationMs)}` : ""}.`; // The failure card on the right always renders a notice now (friendly // message, Homebrew-error guidance, or app-error report), so always point there. @@ -222,19 +239,19 @@ } function failureTitle(label: string): string { - if (label.startsWith("Upgrading ")) return "Upgrade failed"; - if (label.startsWith("Installing ")) return "Install failed"; - if (label.startsWith("Uninstalling ")) return "Uninstall failed"; - return `${label} failed`; + if (label.startsWith("Upgrading ") || label.startsWith("Обновляем ")) return ui.locale === "ru" ? "Не удалось обновить" : "Upgrade failed"; + if (label.startsWith("Installing ") || label.startsWith("Устанавливаем ")) return ui.locale === "ru" ? "Не удалось установить" : "Install failed"; + if (label.startsWith("Uninstalling ") || label.startsWith("Удаляем ")) return ui.locale === "ru" ? "Не удалось удалить" : "Uninstall failed"; + return ui.locale === "ru" ? `Не удалось выполнить: ${label}` : `${label} failed`; } {#if ui.drawerOpen}
-
{#if activeJob?.status === "running"} - {/if} - -
@@ -275,7 +292,7 @@ >
- {p.phase}{p.package ? ` ${p.package}` : ""}{p.total ? ` (${p.current} of ${p.total})` : ""} + {p.phase}{p.package ? ` ${p.package}` : ""}{p.total ? (ui.locale === "ru" ? ` (${p.current} из ${p.total})` : ` (${p.current} of ${p.total})`) : ""} {/if} @@ -292,9 +309,9 @@
{#if !activeJob} -

Quiet. brew commands run by brew-browser appear here.

+

{ui.locale === "ru" ? "Тихо. Команды brew, запущенные из brew-browser, появятся здесь." : "Quiet. brew commands run by brew-browser appear here."}

{:else if activeJob.lines.length === 0} -

Waiting for output…

+

{ui.locale === "ru" ? "Ждём вывод…" : "Waiting for output…"}

{:else} {#each activeJob.lines as line, i (i)} {@const cls = classifyLine(line.text)} @@ -307,11 +324,11 @@ (and reliably) even when the streaming log was muted mid-surge. --> {/if} @@ -319,7 +336,7 @@
{#if activeJob?.status === "failed"} -