Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ jobs:
OFFLOAD_SKIP_DOCKER: "1"
run: swift run -c release OffloadChecks

# У каждой строки интерфейса должен быть английский перевод.
- name: Переводы
run: scripts/check-l10n.sh

# Universal и упаковка — ровно то, что делает release.yml на теге.
# Иначе первая же проверка этих шагов случалась бы на настоящем релизе.
- name: Сборка OffLoadAI.app (universal)
Expand Down
6 changes: 6 additions & 0 deletions Localization/en.lproj/InfoPlist.strings
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* Запросы доступа macOS — по-английски. */
"NSDocumentsFolderUsageDescription" = "OffLoadAI shows what takes up space in your Documents folder and moves what you choose to an external drive.";
"NSDesktopFolderUsageDescription" = "OffLoadAI shows what takes up space on your desktop and moves what you choose to an external drive.";
"NSDownloadsFolderUsageDescription" = "OffLoadAI shows what takes up space in Downloads and moves what you choose to an external drive.";
"NSRemovableVolumesUsageDescription" = "OffLoadAI moves data and makes backups to an external drive.";
"NSNetworkVolumesUsageDescription" = "OffLoadAI can move data to a network drive.";
1,086 changes: 1,086 additions & 0 deletions Localization/en.lproj/Localizable.strings

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions Localization/ru.lproj/InfoPlist.strings
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* Запросы доступа macOS — по-русски (основной Info.plist тоже на русском, но язык разработки — английский). */
"NSDocumentsFolderUsageDescription" = "OffLoadAI показывает, что занимает место в папке «Документы», и переносит выбранное на внешний диск.";
"NSDesktopFolderUsageDescription" = "OffLoadAI показывает, что занимает место на рабочем столе, и переносит выбранное на внешний диск.";
"NSDownloadsFolderUsageDescription" = "OffLoadAI показывает, что занимает место в «Загрузках», и переносит выбранное на внешний диск.";
"NSRemovableVolumesUsageDescription" = "OffLoadAI переносит данные и делает бэкап на внешний диск.";
"NSNetworkVolumesUsageDescription" = "OffLoadAI может переносить данные на сетевой диск.";
2 changes: 2 additions & 0 deletions Localization/ru.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* Русский — язык, на котором написан интерфейс: ключи и есть русский текст, переводить нечего.
Файл нужен, чтобы macOS знала, что русский у программы есть, и выбирала его русским пользователям. */
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,12 @@ XCTest и swift-testing входят только в Xcode, поэтому пр

Переменные `OFFLOAD_SKIP_INTEGRATION=1` и `OFFLOAD_SKIP_DOCKER=1` отключают соответствующие части.

### Переводы

Интерфейс Mac — на русском и английском, язык выбирает macOS: русским — русский, всем остальным — английский. Русский текст в коде и есть ключ перевода: `Text("…")`, `tr("…")` для строк, которые собираются в коде, `trDynamic(…)` для значений вроде `rawValue`. Английский — в `Localization/en.lproj/Localizable.strings`. `scripts/check-l10n.sh` собирает ключи компилятором и проверяет, что у каждого есть перевод с теми же форматами (`%@`, `%lld`); его гоняет CI. Имена файлов на диске, шаблоны поиска и текст для модели помощника не переводятся.

Запустить на другом языке, не меняя системный: `dist/OffLoadAI.app/Contents/MacOS/OffLoadAI -AppleLanguages '(en)'`.

### Снимки экранов

Для проверки интерфейса и скриншотов в README у приложения есть режим снимков: оно проходит по всем разделам, сохраняет каждый в PNG и завершается. Работает только вместе с демонстрационным режимом (`OFFLOAD_DEMO=1`, см. ниже): иначе любая программа могла бы запустить OffLoadAI с полным доступом к диску и получить снимки ваших настоящих папок.
Expand Down Expand Up @@ -258,4 +264,4 @@ Install:
curl -fsSL https://raw.githubusercontent.com/audit0/Offload/main/scripts/install.sh | zsh
```

Build from source with Xcode Command Line Tools: `swift run OffloadChecks`, then `scripts/install-local.sh`. With Command Line Tools 27+ and no Xcode, plain `swift build` cannot build the SwiftUI layer (the `@State` macro plugin ships with Xcode); `scripts/build-app.sh` falls back to the SDK 26 that ships with the same tools. A Windows version (WPF, .NET 10, BitLocker-encrypted VHDX vault) lives in `windows/`; install it with `irm https://raw.githubusercontent.com/audit0/Offload/main/windows/scripts/install.ps1 | iex`. The UI is in Russian. See [SECURITY.md](SECURITY.md) for the threat model. MIT licensed.
Build from source with Xcode Command Line Tools: `swift run OffloadChecks`, then `scripts/install-local.sh`. With Command Line Tools 27+ and no Xcode, plain `swift build` cannot build the SwiftUI layer (the `@State` macro plugin ships with Xcode); `scripts/build-app.sh` falls back to the SDK 26 that ships with the same tools. A Windows version (WPF, .NET 10, BitLocker-encrypted VHDX vault) lives in `windows/`; install it with `irm https://raw.githubusercontent.com/audit0/Offload/main/windows/scripts/install.ps1 | iex`. The Mac UI is in English and Russian and follows the system language (Russian for Russian, English for everyone else); the Windows UI is in Russian for now. See [SECURITY.md](SECURITY.md) for the threat model. MIT licensed.
24 changes: 13 additions & 11 deletions Sources/Offload/AppModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ enum SidebarSection: String, CaseIterable, Identifiable, Hashable {

var title: String {
switch self {
case .overview: return "Обзор"
case .cleanup: return "Разобрать"
case .assistant: return "Помощник"
case .safe: return "Сейф"
case .space: return "Освободить место"
case .history: return "Перенесённое"
case .backup: return "Бэкап"
case .icloud: return "Из iCloud"
case .overview: return tr("Обзор")
case .cleanup: return tr("Разобрать")
case .assistant: return tr("Помощник")
case .safe: return tr("Сейф")
case .space: return tr("Освободить место")
case .history: return tr("Перенесённое")
case .backup: return tr("Бэкап")
case .icloud: return tr("Из iCloud")
case .docker: return "Docker"
}
}
Expand All @@ -43,7 +43,7 @@ enum StoreMode: String, CaseIterable, Identifiable {
case safe, open

var id: Self { self }
var title: String { self == .safe ? "В сейф" : "Открыто на диск" }
var title: String { self == .safe ? tr("В сейф") : tr("Открыто на диск") }
}

@MainActor
Expand Down Expand Up @@ -99,9 +99,9 @@ final class AppModel {

/// Почему класть некуда — одной фразой, с тем, что сделать.
var targetProblem: String? {
guard destination != nil else { return "Подключите внешний диск." }
guard destination != nil else { return tr("Подключите внешний диск.") }
guard storeMode == .safe, safeVolume == nil else { return nil }
return safe.exists ? "Сейф закрыт — откройте его паролем." : "На диске нет сейфа — создайте его в разделе «Сейф»."
return safe.exists ? tr("Сейф закрыт — откройте его паролем.") : tr("На диске нет сейфа — создайте его в разделе «Сейф».")
}

/// Где искать журналы переносов: подключённые диски и открытый сейф.
Expand Down Expand Up @@ -250,6 +250,8 @@ func relativeToHome(_ path: String, home: URL) -> String {

/// 1 объект, 3 объекта, 5 объектов.
func pluralRu(_ count: Int, _ one: String, _ few: String, _ many: String) -> String {
// По-английски форм две: перевод «одного» для 1 и перевод «многих» для остального.
if !AppLanguage.isRussian { return abs(count) == 1 ? trDynamic(one) : trDynamic(many) }
let hundreds = abs(count) % 100
let tens = hundreds % 10
if (11...14).contains(hundreds) { return many }
Expand Down
Loading
Loading