feat(web): storage admin, search and catalog pages (M2)
- Storage admin: local folders with a folder picker and NFS sources, connection test, scan and full scan with live progress, enable, disable, delete - path pattern editor with reorder, examples and a preview table that shows which pattern matched and what came from tags; compile errors land under the broken pattern - Library page with counters, scans and unreadable files; search reindex for admins - search box in the top bar: combobox with keyboard navigation, 150 ms debounce, cancelled stale requests, "/" and Ctrl+K, layout fix hint - search page with tabs and paging, artist and album pages, album cards and track lists; unavailable tracks are dimmed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c1160cccd3
commit
a685b1b7ca
27 files changed
+2505
-9
No files matched your search
@@ -62,5 +62,92 @@
|
||||
"user.password_reset": "Password reset",
|
||||
"user.delete": "User deleted"
|
||||
}
|
||||
},
|
||||
"sources": {
|
||||
"title": "Storage",
|
||||
"subtitle": "Where the server takes music from: a folder on the server or NFS",
|
||||
"add": "Add storage",
|
||||
"empty": "No storage yet. Connect a music folder to get started.",
|
||||
"type_local": "Server folder",
|
||||
"type_nfs": "NFS",
|
||||
"status_pending": "Connecting",
|
||||
"status_mounted": "Connected",
|
||||
"status_error": "Error",
|
||||
"status_disabled": "Disabled",
|
||||
"last_scan": "Last scan: {{date}}",
|
||||
"never_scanned": "Not scanned yet",
|
||||
"scan": "Scan",
|
||||
"full_scan": "Full scan",
|
||||
"test": "Test",
|
||||
"patterns": "Path patterns",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"disable": "Disable",
|
||||
"enable": "Enable",
|
||||
"delete_confirm": "Delete “{{name}}”? Tracks that exist only in this storage will disappear from the library along with their likes.",
|
||||
"scanning": "Scanning: {{processed}} of {{total}}",
|
||||
"scan_walking": "Walking folders…",
|
||||
"scan_done": "Found {{found}} · new {{added}} · updated {{updated}} · moved {{moved}} · missing {{missing}} · errors {{errors}}",
|
||||
"scan_failed": "Scan stopped: {{error}}",
|
||||
"test_ok": "Reachable, audio files found: {{count}}",
|
||||
"test_fail": "Not reachable: {{message}}",
|
||||
"form_title_new": "New storage",
|
||||
"form_title_edit": "Storage",
|
||||
"name": "Name",
|
||||
"type": "Type",
|
||||
"folder": "Folder",
|
||||
"folder_root": "Whole folder",
|
||||
"folder_hint": "A subfolder of LIBRARY_HOST on the server",
|
||||
"up": "Up",
|
||||
"nfs_server": "Server",
|
||||
"nfs_server_hint": "Host name or IP, e.g. nas.lan",
|
||||
"nfs_export": "Export",
|
||||
"nfs_export_hint": "Path on the server, e.g. /export/music",
|
||||
"nfs_options": "Mount options",
|
||||
"nfs_options_hint": "Read-only is added automatically",
|
||||
"tag_priority": "Prefer",
|
||||
"tags_first": "File tags",
|
||||
"path_first": "Path patterns",
|
||||
"interval": "Auto scan",
|
||||
"interval_off": "Manual",
|
||||
"interval_hours": "Every {{count}} h",
|
||||
"save": "Save"
|
||||
},
|
||||
"patterns": {
|
||||
"title": "Path patterns: {{name}}",
|
||||
"intro": "RE2 regular expressions with named groups are matched against the file path relative to the storage root. The first match wins.",
|
||||
"groups": "Groups: artist, albumartist, album, year, disc, track, title, genre; skip ignores the file.",
|
||||
"add": "Add pattern",
|
||||
"regex": "Pattern {{n}}",
|
||||
"enabled": "Enabled",
|
||||
"up": "Up",
|
||||
"down": "Down",
|
||||
"remove": "Remove",
|
||||
"preview": "Preview",
|
||||
"save": "Save",
|
||||
"saved": "Saved. Run a full scan to apply it to files already found.",
|
||||
"col_path": "Path",
|
||||
"col_pattern": "Pattern",
|
||||
"col_result": "Result",
|
||||
"no_match": "no match",
|
||||
"skipped": "skipped",
|
||||
"unmatched": "No pattern matched: {{count}} of {{total}}",
|
||||
"from_tags": "from tags",
|
||||
"error_at": "Error in pattern {{n}}: {{reason}}",
|
||||
"examples": "Examples"
|
||||
},
|
||||
"library": {
|
||||
"title": "Library",
|
||||
"subtitle": "Catalog and scan status",
|
||||
"artists": "Artists",
|
||||
"albums": "Albums",
|
||||
"tracks": "Tracks",
|
||||
"files": "Files",
|
||||
"missing": "Missing files",
|
||||
"errors": "Unreadable",
|
||||
"sources": "Storage",
|
||||
"error_files": "Files with errors",
|
||||
"reindex": "Rebuild search index",
|
||||
"reindex_started": "Rebuild started"
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,9 @@
|
||||
"home": "Home",
|
||||
"admin": "Admin",
|
||||
"users": "Users",
|
||||
"audit": "Audit log"
|
||||
"audit": "Audit log",
|
||||
"library": "Library",
|
||||
"sources": "Storage"
|
||||
},
|
||||
"profile": {
|
||||
"menu": "Profile menu",
|
||||
@@ -79,5 +81,41 @@
|
||||
"loading": "Loading…",
|
||||
"route_error": {
|
||||
"reload": "Reload the page"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Artists, albums, tracks",
|
||||
"label": "Search",
|
||||
"artists": "Artists",
|
||||
"albums": "Albums",
|
||||
"tracks": "Tracks",
|
||||
"all": "All",
|
||||
"nothing": "Nothing found",
|
||||
"corrected": "Showing results for “{{query}}”",
|
||||
"show_all": "All results",
|
||||
"title": "Search: {{query}}",
|
||||
"more": "Show more",
|
||||
"artist_kind": "Artist",
|
||||
"hint": "Press / to search"
|
||||
},
|
||||
"catalog": {
|
||||
"albums": "Albums",
|
||||
"appears_on": "Appears on",
|
||||
"album_count_one": "{{count}} album",
|
||||
"album_count_other": "{{count}} albums",
|
||||
"album_count_few": "{{count}} albums",
|
||||
"album_count_many": "{{count}} albums",
|
||||
"track_count_one": "{{count}} track",
|
||||
"track_count_other": "{{count}} tracks",
|
||||
"track_count_few": "{{count}} tracks",
|
||||
"track_count_many": "{{count}} tracks",
|
||||
"unavailable": "Unavailable: storage is offline or the file is gone",
|
||||
"not_found": "Nothing here — it may have been removed by the last scan",
|
||||
"disc": "Disc {{n}}",
|
||||
"col_title": "Title",
|
||||
"col_album": "Album",
|
||||
"col_duration": "Time",
|
||||
"back_home": "Back home",
|
||||
"artist": "Artist",
|
||||
"album": "Album"
|
||||
}
|
||||
}
|
||||
@@ -38,10 +38,28 @@
|
||||
"ui_prefs_invalid": "Invalid setting value",
|
||||
"client_type_invalid": "Unknown app type",
|
||||
"role_invalid": "Unknown role",
|
||||
"cursor_invalid": "The list is outdated, please reload"
|
||||
"cursor_invalid": "The list is outdated, please reload",
|
||||
"pattern_invalid": "Pattern error: {{reason}}",
|
||||
"nfs_invalid": "Invalid value",
|
||||
"local_path_invalid": "Folder not found",
|
||||
"name_invalid": "The name must be 1 to {{max}} characters",
|
||||
"scan_interval_invalid": "The interval must be at least {{min}} minutes",
|
||||
"source_type_invalid": "Unknown storage type",
|
||||
"query_empty": "Enter a query"
|
||||
},
|
||||
"users": {
|
||||
"not_found": "User not found",
|
||||
"last_admin": "The last administrator cannot be demoted, blocked or deleted"
|
||||
},
|
||||
"library": {
|
||||
"source_not_found": "Storage not found",
|
||||
"scan_running": "A scan is already running",
|
||||
"source_offline": "The storage is not connected"
|
||||
},
|
||||
"catalog": {
|
||||
"not_found": "Not found"
|
||||
},
|
||||
"search": {
|
||||
"unavailable": "Search is temporarily unavailable"
|
||||
}
|
||||
}
|
||||
@@ -62,5 +62,92 @@
|
||||
"user.password_reset": "Сброшен пароль",
|
||||
"user.delete": "Пользователь удалён"
|
||||
}
|
||||
},
|
||||
"sources": {
|
||||
"title": "Хранилища",
|
||||
"subtitle": "Откуда сервер берёт музыку: папка на диске сервера или NFS",
|
||||
"add": "Подключить хранилище",
|
||||
"empty": "Хранилищ пока нет. Подключите папку с музыкой, чтобы начать.",
|
||||
"type_local": "Папка на сервере",
|
||||
"type_nfs": "NFS",
|
||||
"status_pending": "Подключается",
|
||||
"status_mounted": "Подключено",
|
||||
"status_error": "Ошибка",
|
||||
"status_disabled": "Отключено",
|
||||
"last_scan": "Последний скан: {{date}}",
|
||||
"never_scanned": "Ещё не сканировалось",
|
||||
"scan": "Сканировать",
|
||||
"full_scan": "Полный скан",
|
||||
"test": "Проверить",
|
||||
"patterns": "Шаблоны путей",
|
||||
"edit": "Изменить",
|
||||
"delete": "Удалить",
|
||||
"disable": "Отключить",
|
||||
"enable": "Подключить",
|
||||
"delete_confirm": "Удалить «{{name}}»? Треки, которые есть только в этом хранилище, пропадут из библиотеки вместе с лайками.",
|
||||
"scanning": "Сканирование: {{processed}} из {{total}}",
|
||||
"scan_walking": "Обход папок…",
|
||||
"scan_done": "Найдено {{found}} · новых {{added}} · обновлено {{updated}} · перемещено {{moved}} · пропало {{missing}} · ошибок {{errors}}",
|
||||
"scan_failed": "Скан прервался: {{error}}",
|
||||
"test_ok": "Доступно, найдено аудиофайлов: {{count}}",
|
||||
"test_fail": "Нет доступа: {{message}}",
|
||||
"form_title_new": "Новое хранилище",
|
||||
"form_title_edit": "Хранилище",
|
||||
"name": "Название",
|
||||
"type": "Тип",
|
||||
"folder": "Папка",
|
||||
"folder_root": "Весь каталог",
|
||||
"folder_hint": "Подпапка каталога LIBRARY_HOST на сервере",
|
||||
"up": "Вверх",
|
||||
"nfs_server": "Сервер",
|
||||
"nfs_server_hint": "Имя или IP, например nas.lan",
|
||||
"nfs_export": "Экспорт",
|
||||
"nfs_export_hint": "Путь на сервере, например /export/music",
|
||||
"nfs_options": "Опции монтирования",
|
||||
"nfs_options_hint": "Только чтение добавляется автоматически",
|
||||
"tag_priority": "Что важнее",
|
||||
"tags_first": "Теги файлов",
|
||||
"path_first": "Шаблоны путей",
|
||||
"interval": "Автоскан",
|
||||
"interval_off": "Вручную",
|
||||
"interval_hours": "Каждые {{count}} ч",
|
||||
"save": "Сохранить"
|
||||
},
|
||||
"patterns": {
|
||||
"title": "Шаблоны путей: {{name}}",
|
||||
"intro": "Регулярные выражения RE2 с именованными группами применяются к пути файла относительно корня хранилища. Побеждает первое совпавшее.",
|
||||
"groups": "Группы: artist, albumartist, album, year, disc, track, title, genre; skip — игнорировать файл.",
|
||||
"add": "Добавить шаблон",
|
||||
"regex": "Шаблон {{n}}",
|
||||
"enabled": "Включён",
|
||||
"up": "Выше",
|
||||
"down": "Ниже",
|
||||
"remove": "Удалить",
|
||||
"preview": "Предпросмотр",
|
||||
"save": "Сохранить",
|
||||
"saved": "Сохранено. Запустите полный скан, чтобы применить к уже найденным файлам.",
|
||||
"col_path": "Путь",
|
||||
"col_pattern": "Шаблон",
|
||||
"col_result": "Результат",
|
||||
"no_match": "не совпал",
|
||||
"skipped": "пропускается",
|
||||
"unmatched": "Не совпал ни один шаблон: {{count}} из {{total}}",
|
||||
"from_tags": "из тегов",
|
||||
"error_at": "Ошибка в шаблоне {{n}}: {{reason}}",
|
||||
"examples": "Примеры"
|
||||
},
|
||||
"library": {
|
||||
"title": "Библиотека",
|
||||
"subtitle": "Состояние каталога и сканирования",
|
||||
"artists": "Исполнителей",
|
||||
"albums": "Альбомов",
|
||||
"tracks": "Треков",
|
||||
"files": "Файлов",
|
||||
"missing": "Пропавших файлов",
|
||||
"errors": "Не прочитано",
|
||||
"sources": "Хранилища",
|
||||
"error_files": "Файлы с ошибками",
|
||||
"reindex": "Переиндексировать поиск",
|
||||
"reindex_started": "Переиндексация запущена"
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,9 @@
|
||||
"home": "Главная",
|
||||
"admin": "Админка",
|
||||
"users": "Пользователи",
|
||||
"audit": "Журнал"
|
||||
"audit": "Журнал",
|
||||
"library": "Библиотека",
|
||||
"sources": "Хранилища"
|
||||
},
|
||||
"profile": {
|
||||
"menu": "Меню профиля",
|
||||
@@ -79,5 +81,41 @@
|
||||
"loading": "Загрузка…",
|
||||
"route_error": {
|
||||
"reload": "Обновить страницу"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Исполнители, альбомы, треки",
|
||||
"label": "Поиск",
|
||||
"artists": "Исполнители",
|
||||
"albums": "Альбомы",
|
||||
"tracks": "Треки",
|
||||
"all": "Всё",
|
||||
"nothing": "Ничего не нашли",
|
||||
"corrected": "Показаны результаты для «{{query}}»",
|
||||
"show_all": "Все результаты",
|
||||
"title": "Поиск: {{query}}",
|
||||
"more": "Показать ещё",
|
||||
"artist_kind": "Исполнитель",
|
||||
"hint": "Нажмите / для поиска"
|
||||
},
|
||||
"catalog": {
|
||||
"albums": "Альбомы",
|
||||
"appears_on": "Встречается на",
|
||||
"album_count_one": "{{count}} альбом",
|
||||
"album_count_few": "{{count}} альбома",
|
||||
"album_count_many": "{{count}} альбомов",
|
||||
"album_count_other": "{{count}} альбома",
|
||||
"track_count_one": "{{count}} трек",
|
||||
"track_count_few": "{{count}} трека",
|
||||
"track_count_many": "{{count}} треков",
|
||||
"track_count_other": "{{count}} трека",
|
||||
"unavailable": "Недоступен: хранилище отключено или файл пропал",
|
||||
"not_found": "Ничего не найдено — возможно, удалено при последнем скане",
|
||||
"disc": "Диск {{n}}",
|
||||
"col_title": "Название",
|
||||
"col_album": "Альбом",
|
||||
"col_duration": "Время",
|
||||
"back_home": "На главную",
|
||||
"artist": "Исполнитель",
|
||||
"album": "Альбом"
|
||||
}
|
||||
}
|
||||
@@ -38,10 +38,28 @@
|
||||
"ui_prefs_invalid": "Недопустимое значение настройки",
|
||||
"client_type_invalid": "Неизвестный тип приложения",
|
||||
"role_invalid": "Неизвестная роль",
|
||||
"cursor_invalid": "Список устарел, обновите страницу"
|
||||
"cursor_invalid": "Список устарел, обновите страницу",
|
||||
"pattern_invalid": "Ошибка в шаблоне: {{reason}}",
|
||||
"nfs_invalid": "Недопустимое значение",
|
||||
"local_path_invalid": "Папка не найдена",
|
||||
"name_invalid": "Название — от 1 до {{max}} символов",
|
||||
"scan_interval_invalid": "Интервал — не меньше {{min}} минут",
|
||||
"source_type_invalid": "Неизвестный тип хранилища",
|
||||
"query_empty": "Введите запрос"
|
||||
},
|
||||
"users": {
|
||||
"not_found": "Пользователь не найден",
|
||||
"last_admin": "Нельзя понизить, заблокировать или удалить последнего администратора"
|
||||
},
|
||||
"library": {
|
||||
"source_not_found": "Хранилище не найдено",
|
||||
"scan_running": "Скан уже идёт",
|
||||
"source_offline": "Хранилище не подключено"
|
||||
},
|
||||
"catalog": {
|
||||
"not_found": "Не найдено"
|
||||
},
|
||||
"search": {
|
||||
"unavailable": "Поиск временно недоступен"
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user