refactor(gui): разделить app.go на binding-файлы по доменам, вынести sync apply

- app.go (1810→280 строк): только App struct, startup, DTOs, helpers
- bindings_{nodes,files,notes,actions,worklog,activity,sync,settings}.go
- sync_apply.go: все applyRemote* методы
- i18n: internal/i18n (Go, embed JSON) + frontend/src/lib/i18n (JS)
- core/sync/safe_path.go: SafeVaultPath
- scripts/check-i18n.sh: проверка хардкода кириллицы и bidi-символов
- build.sh: NVM loading, set -e

Все сборки (CLI, server, gui, frontend), go vet, go test проходят.
This commit is contained in:
2026-06-02 10:47:38 +08:00
parent 390d451977
commit 3089d777a8
20 changed files with 2658 additions and 1603 deletions
+101
View File
@@ -0,0 +1,101 @@
{
"nav.today": "Today",
"nav.inbox": "Inbox",
"nav.activity": "Activity",
"nav.clients": "Clients",
"nav.projects": "Projects",
"nav.recipes": "Recipes",
"nav.documents": "Documents",
"nav.archive": "Archive",
"tab.overview": "Overview",
"tab.notes": "Notes",
"tab.files": "Files",
"tab.actions": "Actions",
"tab.worklog": "Work Log",
"tab.activity": "Activity",
"common.save": "Save",
"common.cancel": "Cancel",
"common.delete": "Delete",
"common.rename": "Rename",
"common.close": "Close",
"common.create": "Create",
"common.confirm": "Confirm",
"common.back": "← Back",
"common.loading": "Loading...",
"common.error": "Error:",
"common.yes": "Yes",
"common.ok": "OK",
"common.run": "Run",
"common.name": "Name",
"common.settings": "Settings",
"welcome.title": "Verstak",
"welcome.selectSection": "Select a section in the sidebar.",
"welcome.addCase": "Add case",
"event.noteCreated": "Note created",
"event.noteUpdated": "Note updated",
"event.fileAdded": "File added",
"event.fileDeleted": "File deleted",
"event.fileRenamed": "File renamed",
"event.fileCopied": "File copied",
"event.fileMoved": "File moved",
"event.caseCreated": "Case created",
"action.openUrl": "Open URL",
"action.openFile": "Open file",
"action.openFolder": "Open folder",
"action.runCommand": "Run command",
"action.runScript": "Run script",
"action.openTerminal": "Open terminal",
"action.launchApp": "Launch app",
"note.add": "+ Add note",
"note.noNotes": "No notes",
"note.title": "Note title",
"note.placeholder": "Start writing...",
"file.addFile": "+ Add file",
"file.addFolder": "+ Add folder",
"file.preview": "Preview",
"file.openExternal": "Open in external program",
"file.openFolder": "Open folder",
"file.delete": "Delete",
"file.pickSingle": "Select file",
"file.pickDirectory": "Select folder",
"sync.title": "Sync",
"sync.settings": "Sync settings",
"sync.status": "Status",
"sync.server": "Server",
"sync.device": "Device",
"sync.connected": "Connected",
"sync.notConnected": "Not connected",
"sync.disabled": "Disabled",
"server.registerBtn": "Register",
"server.loginBtn": "Log in",
"server.logout": "Log out",
"server.username": "Username",
"server.email": "Email",
"server.password": "Password",
"server.save": "Save",
"server.back": "← Back",
"admin.devices": "Devices",
"admin.users": "Users",
"admin.smtp": "SMTP Settings",
"admin.healthCheck": "Health check",
"admin.status": "Status",
"admin.active": "Active",
"admin.revoked": "Revoked",
"error.generic": "An error occurred",
"error.invalidCredentials": "Invalid username or password",
"error.accountBlocked": "Account blocked",
"error.emailNotConfirmed": "Email not confirmed",
"error.tokenInvalid": "Invalid or expired token",
"error.tokenExpired": "Token expired"
}
+383
View File
@@ -0,0 +1,383 @@
{
"nav.today": "Сегодня",
"nav.inbox": "Неразобранное",
"nav.activity": "Активность",
"nav.clients": "Клиенты",
"nav.projects": "Проекты",
"nav.recipes": "Рецепты",
"nav.documents": "Документы",
"nav.archive": "Архив",
"nav.sections": "Разделы",
"nav.cases": "Дела",
"nav.noCases": "Нет дел",
"nav.sync": "Синхронизация",
"nav.syncSettings": "Настройки синхронизации",
"nav.syncNow": "Синхронизировать",
"nav.selectPrompt": "Выберите раздел или дело",
"nav.brand": "Верстак",
"tab.overview": "Обзор",
"tab.notes": "Заметки",
"tab.files": "Файлы",
"tab.actions": "Действия",
"tab.worklog": "Журнал",
"tab.activity": "Активность",
"common.save": "Сохранить",
"common.cancel": "Отмена",
"common.delete": "Удалить",
"common.rename": "Переименовать",
"common.close": "Закрыть",
"common.create": "Создать",
"common.confirm": "Подтверждение",
"common.back": "← Назад",
"common.loading": "Загрузка...",
"common.error": "Ошибка:",
"common.yes": "Да",
"common.ok": "OK",
"common.copy": "Копировать",
"common.cut": "Вырезать",
"common.paste": "Вставить",
"common.duplicate": "Дублировать",
"common.run": "Запустить",
"common.test": "Test",
"common.testAgain": "Проверить",
"common.connect": "Подключиться",
"common.disconnect": "Отключиться",
"common.settings": "Настройки",
"common.name": "Название",
"common.type": "Тип",
"common.section": "Раздел",
"common.created": "Создано",
"common.empty": "Нет",
"common.newName": "Новое имя",
"welcome.title": "Верстак",
"welcome.selectSection": "Выберите раздел в боковой панели.",
"welcome.createCase": "Или создайте новое дело кнопкой «+».",
"welcome.addCase": "Добавить дело",
"event.noteCreated": "Заметка создана",
"event.noteUpdated": "Заметка изменена",
"event.fileAdded": "Файл добавлен",
"event.fileDeleted": "Файл удалён",
"event.fileRenamed": "Файл переименован",
"event.fileCopied": "Файл скопирован",
"event.fileMoved": "Файл перемещён",
"event.folderAdded": "Папка добавлена",
"event.folderDeleted": "Папка удалена",
"event.folderRenamed": "Папка переименована",
"event.caseCreated": "Дело создано",
"event.caseUpdated": "Дело изменено",
"kind.project": "Проект",
"kind.client": "Клиент",
"kind.document": "Документ",
"kind.recipe": "Рецепт",
"kind.archive": "Архив",
"kind.case": "Дело",
"action.openUrl": "Открыть URL",
"action.openFile": "Открыть файл",
"action.openFolder": "Открыть папку",
"action.runCommand": "Запустить команду",
"action.runScript": "Запустить скрипт",
"action.openTerminal": "Открыть терминал",
"action.launchApp": "Запустить приложение",
"action.addAction": "+ Добавить действие",
"action.newAction": "Новое действие",
"action.noActions": "Действий пока нет",
"action.run": "Запустить",
"action.dataUrl": "URL",
"action.dataPath": "Путь",
"action.dataCommand": "Команда",
"action.urlPlaceholder": "https://example.com",
"action.pathPlaceholder": "/path/to/file",
"action.commandPlaceholder": "команда",
"action.namePlaceholder": "Например: Открыть сайт",
"note.add": "+ Добавить заметку",
"note.new": "Новая заметка",
"note.title": "Название заметки",
"note.noNotes": "Нет заметок",
"note.createFirst": "Создайте первую заметку для этого дела.",
"note.placeholder": "Начните писать...",
"note.unsavedTitle": "Несохранённые изменения",
"note.unsavedMessage": "Закрыть редактор? Все несохранённые изменения будут потеряны.",
"note.unsavedClose": "Закрыть",
"file.addFile": "+ Добавить файл",
"file.addFolder": "+ Добавить папку",
"file.newFile": "+ Новый файл",
"file.addFileSimple": "Добавить файл",
"file.addFolderSimple": "Добавить папку",
"file.noFiles": "В этой папке пока нет файлов",
"file.noFilesCase": "В этом проекте пока нет файлов",
"file.hint": "Добавьте файл или папку, чтобы сохранить материалы проекта.",
"file.root": "Файлы",
"file.preview": "Предпросмотр",
"file.openExternal": "Открыть во внешней программе",
"file.openFolder": "Открыть папку",
"file.showInExplorer": "Показать в проводнике",
"file.more": "Ещё",
"file.delete": "Удалить",
"file.ariaFolder": "Папка",
"file.ariaFile": "Файл",
"file.scanning": "Сканирование...",
"file.pickSingle": "Выберите файл",
"file.pickMultiple": "Выберите файлы",
"file.pickDirectory": "Выберите папку",
"file.importTitle": "Добавить в",
"file.importFiles": "Файлов:",
"file.importFolders": "Папок:",
"file.importSize": "Размер:",
"file.importCopy": "Скопировать",
"file.importLink": "Привязать",
"file.selectCaseFirst": "Сначала выберите дело для добавления файлов",
"worklog.title": "Журнал",
"worklog.whatDone": "Что сделано",
"worklog.minutes": "Мин",
"worklog.min": "мин",
"worklog.log": "Записать",
"worklog.empty": "Записей работы пока нет",
"sync.title": "Синхронизация",
"sync.settings": "Настройки синхронизации",
"sync.status": "Статус",
"sync.server": "Сервер",
"sync.device": "Устройство",
"sync.deviceId": "ID устройства",
"sync.unpushed": "Неотправлено",
"sync.lastSync": "Последняя синх.",
"sync.revoked": "Отозвано",
"sync.connected": "Подключено",
"sync.notConnected": "Не подключено",
"sync.disabled": "Отключена",
"sync.serverUrl": "URL сервера",
"sync.serverUrlPlaceholder": "https://example.com:47732",
"sync.username": "Логин",
"sync.usernamePlaceholder": "username",
"sync.password": "Пароль",
"sync.passwordPlaceholder": "password",
"sync.autoSync": "Автосинхронизация (мин, 0 = отключено)",
"sync.saveInterval": "Сохранить интервал",
"sync.syncNow": "Синхронизировать",
"sync.disconnect": "Отключиться",
"sync.connect": "Подключиться",
"sync.test": "Проверить",
"sync.settingsSaved": "интервал сохранён",
"today.title": "Сегодня",
"today.changedCases": "Изменён сегодня",
"today.timeline": "Лента за сегодня",
"today.empty": "Сегодня пока тихо",
"today.emptyHint": "Здесь появятся дела, заметки, файлы и действия, с которыми вы работали сегодня.",
"today.plural.case_one": "дело",
"today.plural.case_few": "дела",
"today.plural.case_many": "дел",
"today.plural.note_one": "заметка",
"today.plural.note_few": "заметки",
"today.plural.note_many": "заметок",
"today.plural.file_one": "файл",
"today.plural.file_few": "файла",
"today.plural.file_many": "файлов",
"today.plural.event_one": "событие",
"today.plural.event_few": "события",
"today.plural.event_many": "событий",
"activity.title": "Активность",
"activity.empty": "Активность пока не зафиксирована",
"activity.perCaseEmpty": "Активность пока не зафиксирована",
"overview.type": "Тип",
"overview.section": "Раздел",
"overview.created": "Создано",
"overview.newNote": "Новая заметка",
"overview.addFile": "Добавить файл",
"overview.addAction": "Добавить действие",
"overview.logTime": "Записать время",
"overview.recentNotes": "Последние заметки",
"overview.recentEntries": "Последние записи",
"rename.title": "Переименовать",
"rename.emptyError": "Имя не может быть пустым",
"rename.invalidError": "Недопустимое имя",
"delete.confirmTitle": "Удаление",
"delete.confirmMessage": "Удалить",
"delete.folder": "папку",
"delete.file": "файл",
"template.optionNone": "Без шаблона",
"template.optional": "Шаблон (опционально)",
"mime.jpeg": "Изображение JPEG",
"mime.png": "Изображение PNG",
"mime.gif": "Изображение GIF",
"mime.webp": "Изображение WebP",
"mime.svg": "Изображение SVG",
"mime.bmp": "Изображение BMP",
"mime.tiff": "Изображение TIFF",
"mime.avif": "Изображение AVIF",
"mime.pdf": "PDF документ",
"mime.word": "Документ Word",
"mime.excel": "Таблица Excel",
"mime.ppt": "Презентация PowerPoint",
"mime.zip": "ZIP архив",
"mime.gzip": "GZIP архив",
"mime.tar": "TAR архив",
"mime.sevenz": "7z архив",
"mime.rar": "RAR архив",
"mime.text": "Текстовый файл",
"mime.html": "HTML файл",
"mime.css": "CSS файл",
"mime.js": "JavaScript файл",
"mime.json": "JSON файл",
"mime.xml": "XML файл",
"mime.yaml": "YAML файл",
"mime.binary": "Бинарный файл",
"mime.executable": "Исполняемый файл",
"mime.folder": "Папка",
"mime.unknown": "Неизвестно",
"mime.file": "Файл",
"server.register": "Регистрация",
"server.registerTitle": "Verstak Sync — Регистрация",
"server.registerBtn": "Зарегистрироваться",
"server.login": "Вход",
"server.loginTitle": "Verstak Sync — Вход",
"server.loginBtn": "Войти",
"server.logout": "Выйти",
"server.username": "Логин",
"server.usernameOrEmail": "Логин или Email",
"server.email": "Email",
"server.password": "Пароль",
"server.passwordConfirm": "Подтвердите пароль",
"server.passwordHint": "Минимум 8 символов: латинские буквы + цифры",
"server.forgotPassword": "Забыли пароль?",
"server.adminLink": "Администратор?",
"server.alreadyHaveAccount": "Уже есть аккаунт?",
"server.backToLogin": "← Вспомнили пароль?",
"server.goHome": "На главную",
"server.needEmail": "Email обязателен",
"server.allFieldsRequired": "Все поля обязательны",
"server.passwordsDoNotMatch": "Пароли не совпадают",
"server.resetPasswordTitle": "Verstak Sync — Восстановление пароля",
"server.resetPassword": "Восстановление пароля",
"server.resetInstruction": "Введите email, указанный при регистрации",
"server.sendLink": "Отправить ссылку",
"server.emailSentTitle": "Verstak Sync — Письмо отправлено",
"server.emailSent": "✓ Письмо отправлено",
"server.emailSentMessage": "Если указанный email зарегистрирован, на него придёт ссылка для сброса пароля.",
"server.newPasswordTitle": "Verstak Sync — Новый пароль",
"server.newPassword": "Новый пароль",
"server.passwordChanged": "✓ Пароль изменён",
"server.passwordChangedMessage": "Теперь вы можете войти с новым паролем.",
"server.save": "Сохранить",
"server.emailConfirmed": "✓ Email подтверждён",
"server.emailConfirmedMessage": "Ваш email успешно подтверждён. Теперь вы можете войти в систему.",
"server.registrationSuccess": "✓ Регистрация успешна",
"server.registrationEmailSent": "На вашу почту отправлено письмо с подтверждением.",
"server.registrationCheckEmail": "Перейдите по ссылке в письме, чтобы активировать аккаунт.",
"server.registrationAutoSuccess": "✓ Регистрация успешна",
"server.registrationAutoMessage": "Вы можете войти — подтверждение email не требуется.",
"server.back": "← Назад",
"server.dashboard": "← Дашборд",
"server.users": "Пользователи",
"server.adminPwdHint": "Минимум 8 символов, латинские буквы и цифры",
"server.newPasswordResult": "Новый пароль: %s\nСообщите его пользователю.",
"admin.login": "Verstak Sync — Admin Login",
"admin.dashboard": "Verstak Sync — Admin",
"admin.users": "Verstak Sync — Пользователи",
"admin.usersHeading": "Пользователи",
"admin.username": "Логин",
"admin.email": "Email",
"admin.password": "Пароль",
"admin.loginBtn": "Войти",
"admin.devices": "Устройства",
"admin.deviceCount": "Устройств:",
"admin.opsCount": "Операций:",
"admin.smtp": "Настройка SMTP",
"admin.smtpTitle": "SMTP (для писем)",
"admin.smtpServer": "Сервер",
"admin.smtpPort": "Порт",
"admin.smtpType": "Тип",
"admin.smtpNoEncryption": "Без шифрования",
"admin.smtpUsername": "Логин",
"admin.smtpPassword": "Пароль",
"admin.smtpFrom": "От кого",
"admin.smtpServerURL": "URL сервера",
"admin.smtpSave": "Сохранить SMTP",
"admin.smtpTest": "Test",
"admin.smtpTesting": "⏳ Тестируем...",
"admin.smtpPassed": "✓ Тест пройден",
"admin.smtpFailed": "✗",
"admin.healthCheck": "Health check",
"admin.healthLoading": "Загрузка...",
"admin.noDevices": "Нет устройств",
"admin.device": "Устройство",
"admin.user": "Пользователь",
"admin.version": "Версия",
"admin.status": "Статус",
"admin.active": "Активно",
"admin.revoked": "Отозвано",
"admin.lastSeen": "Активность",
"admin.revoke": "Отозвать",
"admin.revokeConfirm": "Отозвать устройство?",
"admin.filterPlaceholder": "Фильтр по логину...",
"admin.actions": "Действия",
"admin.confirmed": "Подтверждён",
"admin.unconfirmed": "Не подтверждён",
"admin.blocked": "Заблокирован",
"admin.unblock": "Разблокировать",
"admin.block": "Заблокировать",
"admin.resetPassword": "Сброс пароля",
"admin.resetPasswordConfirm": "Сбросить пароль?",
"admin.resetPasswordMessage": "Пользователь не сможет войти со старым паролем.",
"admin.resetBtn": "Сбросить",
"admin.editUser": "Редактировать пользователя",
"admin.editBtn": "Сохранить",
"admin.deleteUser": "Удалить пользователя?",
"admin.deleteUserMessage": "Будет удалён пользователь «%s» и все его устройства.",
"admin.deleteBtn": "Удалить",
"admin.resultTitle": "Результат",
"admin.confirmTitle": "Подтверждение",
"admin.modalCancel": "Отмена",
"admin.modalConfirm": "Да",
"admin.noUsers": "Нет пользователей",
"admin.unblockUserTitle": "Разблокировать пользователя?",
"admin.unblockUserMessage": "Пользователь сможет снова войти.",
"admin.blockUserTitle": "Заблокировать пользователя?",
"admin.blockUserMessage": "Пользователь не сможет войти.",
"admin.unblockBtn": "Разблокировать",
"admin.blockBtn": "Заблокировать",
"userDashboard.title": "Verstak Sync",
"userDashboard.devices": "Устройства",
"userDashboard.connectNew": "Подключить новое устройство",
"userDashboard.connectNewHint": "Откройте desktop-клиент Verstak, перейдите в настройки синхронизации и введите URL сервера, логин и пароль.",
"userDashboard.noDevices": "Нет подключённых устройств.<br>Подключите устройство из desktop-клиента Verstak.",
"userDashboard.device": "Устройство",
"userDashboard.status": "Статус",
"userDashboard.connected": "Подключено",
"userDashboard.lastSeen": "Активность",
"userDashboard.version": "Версия",
"userDashboard.active": "Активно",
"userDashboard.revoked": "Отозвано",
"userDashboard.revoke": "Отозвать",
"userDashboard.revokeConfirm": "Отозвать устройство? Оно перестанет синхронизироваться.",
"userDashboard.revokePrompt": "Введите ваш пароль для подтверждения:",
"userDashboard.logout": "Выйти",
"error.nameEmpty": "Имя не может быть пустым",
"error.nameInvalid": "Недопустимое имя",
"error.selectCaseFirst": "Сначала выберите дело",
"error.generic": "Произошла ошибка",
"error.invalidCredentials": "Неверный логин или пароль",
"error.accountBlocked": "Аккаунт заблокирован",
"error.emailNotConfirmed": "Email не подтверждён",
"error.tokenInvalid": "Неверный или просроченный токен",
"error.tokenExpired": "Срок действия токена истёк"
}