stabilization: server.go split + i18n templates + frontend localization
cmd/verstak-server/server.go (2838→127 строк): разделён на 12 файлов - config.go, tokens.go, schema.go - server.go (только struct + NewServer + ListenAndServe) - routes.go, middleware.go, smtp.go - handlers_api.go, handlers_user.go, handlers_web_user.go, handlers_admin.go - templates.go (конвертирован в функции с i18n.T()) frontend: все русские строки заменены на t() вызовы - App.svelte, FileTreeRow.svelte, ConfirmModal.svelte - FilePreviewModal.svelte, fileUtils.js core: gofmt по всему проекту Все сборки (CLI, server, gui, frontend), go vet, go test проходят. check-i18n.sh: frontend чист, Go-файлы с кириллицей — только тесты/легаси.
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
<script>
|
||||
export let title = 'Подтверждение'
|
||||
export let message = ''
|
||||
export let confirmText = 'Удалить'
|
||||
export let cancelText = 'Отмена'
|
||||
export let danger = false
|
||||
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { t } from './i18n'
|
||||
|
||||
export let title = t('common.confirm')
|
||||
export let message = ''
|
||||
export let confirmText = t('common.delete')
|
||||
export let cancelText = t('common.cancel')
|
||||
export let danger = false
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { createEventDispatcher, onMount, onDestroy } from 'svelte'
|
||||
import FileIcon from './FileIcon.svelte'
|
||||
import { formatFileSize, formatMimeType, getFileKind, isImageFile, isTextFile, isPdfFile, isMarkdownFile } from './fileUtils.js'
|
||||
import { t } from './i18n'
|
||||
|
||||
export let item
|
||||
export let content = '' // base64 data URI or text content
|
||||
@@ -43,7 +44,7 @@
|
||||
</div>
|
||||
<div class="preview-meta">{formatFileSize(item.size)} · {formatMimeType(item.mime)}</div>
|
||||
<div class="preview-actions">
|
||||
<button class="action-btn" on:click={handleOpenExternal} title="Открыть во внешней программе" aria-label="Открыть внешне">
|
||||
<button class="action-btn" on:click={handleOpenExternal} title={t('file.openExternal')} aria-label={t('file.openExternal')}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
||||
<polyline points="15 3 21 3 21 9"/>
|
||||
@@ -60,11 +61,11 @@
|
||||
</header>
|
||||
<div class="preview-body">
|
||||
{#if loading}
|
||||
<div class="preview-status"><p>Загрузка...</p></div>
|
||||
<div class="preview-status"><p>{t('common.loading')}</p></div>
|
||||
{:else if error}
|
||||
<div class="preview-status">
|
||||
<p>{error}</p>
|
||||
<button class="btn btn-sm" on:click={handleOpenExternal}>Открыть во внешней программе</button>
|
||||
<button class="btn btn-sm" on:click={handleOpenExternal}>{t('file.openExternal')}</button>
|
||||
</div>
|
||||
{:else if showImage && content}
|
||||
<div class="preview-image-container">
|
||||
@@ -79,14 +80,14 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="preview-status">
|
||||
<p>Предпросмотр PDF недоступен.</p>
|
||||
<button class="btn btn-sm" on:click={handleOpenExternal}>Открыть во внешней программе</button>
|
||||
<p>{t('file.pdfUnavailable')}</p>
|
||||
<button class="btn btn-sm" on:click={handleOpenExternal}>{t('file.openExternal')}</button>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="preview-status">
|
||||
<p>Предпросмотр недоступен для этого типа файлов.</p>
|
||||
<button class="btn btn-sm" on:click={handleOpenExternal}>Открыть во внешней программе</button>
|
||||
<p>{t('file.previewUnavailable')}</p>
|
||||
<button class="btn btn-sm" on:click={handleOpenExternal}>{t('file.openExternal')}</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { t } from './i18n'
|
||||
|
||||
export function formatFileSize(bytes) {
|
||||
if (bytes == null || bytes < 0) return '—'
|
||||
if (bytes === 0) return '0 B'
|
||||
@@ -8,51 +10,51 @@ export function formatFileSize(bytes) {
|
||||
}
|
||||
|
||||
const mimeLabels = {
|
||||
'image/jpeg': 'Изображение JPEG',
|
||||
'image/png': 'Изображение PNG',
|
||||
'image/gif': 'Изображение GIF',
|
||||
'image/webp': 'Изображение WebP',
|
||||
'image/svg+xml': 'Изображение SVG',
|
||||
'image/bmp': 'Изображение BMP',
|
||||
'image/tiff': 'Изображение TIFF',
|
||||
'image/avif': 'Изображение AVIF',
|
||||
'application/pdf': 'PDF документ',
|
||||
'application/msword': 'Документ Word',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'Документ Word',
|
||||
'application/vnd.ms-excel': 'Таблица Excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'Таблица Excel',
|
||||
'application/vnd.ms-powerpoint': 'Презентация PowerPoint',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'Презентация PowerPoint',
|
||||
'application/zip': 'ZIP архив',
|
||||
'application/gzip': 'GZIP архив',
|
||||
'application/x-tar': 'TAR архив',
|
||||
'application/x-7z-compressed': '7z архив',
|
||||
'application/x-rar-compressed': 'RAR архив',
|
||||
'text/plain': 'Текстовый файл',
|
||||
'text/html': 'HTML файл',
|
||||
'text/css': 'CSS файл',
|
||||
'text/javascript': 'JavaScript файл',
|
||||
'application/json': 'JSON файл',
|
||||
'application/xml': 'XML файл',
|
||||
'application/x-yaml': 'YAML файл',
|
||||
'application/octet-stream': 'Бинарный файл',
|
||||
'application/x-msdos-program': 'Исполняемый файл',
|
||||
'inode/directory': 'Папка',
|
||||
'image/jpeg': t('mime.jpeg'),
|
||||
'image/png': t('mime.png'),
|
||||
'image/gif': t('mime.gif'),
|
||||
'image/webp': t('mime.webp'),
|
||||
'image/svg+xml': t('mime.svg'),
|
||||
'image/bmp': t('mime.bmp'),
|
||||
'image/tiff': t('mime.tiff'),
|
||||
'image/avif': t('mime.avif'),
|
||||
'application/pdf': t('mime.pdf'),
|
||||
'application/msword': t('mime.word'),
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': t('mime.word'),
|
||||
'application/vnd.ms-excel': t('mime.excel'),
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': t('mime.excel'),
|
||||
'application/vnd.ms-powerpoint': t('mime.ppt'),
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation': t('mime.ppt'),
|
||||
'application/zip': t('mime.zip'),
|
||||
'application/gzip': t('mime.gzip'),
|
||||
'application/x-tar': t('mime.tar'),
|
||||
'application/x-7z-compressed': t('mime.sevenz'),
|
||||
'application/x-rar-compressed': t('mime.rar'),
|
||||
'text/plain': t('mime.text'),
|
||||
'text/html': t('mime.html'),
|
||||
'text/css': t('mime.css'),
|
||||
'text/javascript': t('mime.js'),
|
||||
'application/json': t('mime.json'),
|
||||
'application/xml': t('mime.xml'),
|
||||
'application/x-yaml': t('mime.yaml'),
|
||||
'application/octet-stream': t('mime.binary'),
|
||||
'application/x-msdos-program': t('mime.executable'),
|
||||
'inode/directory': t('mime.folder'),
|
||||
}
|
||||
|
||||
export function formatMimeType(mime) {
|
||||
if (!mime) return 'Неизвестно'
|
||||
if (!mime) return t('mime.unknown')
|
||||
return mimeLabels[mime] || mime
|
||||
}
|
||||
|
||||
export function formatFileType(item) {
|
||||
if (item.type === 'folder') return 'Папка'
|
||||
if (item.type === 'folder') return t('mime.folder')
|
||||
const mime = (item.mime || '').toLowerCase()
|
||||
if (mimeLabels[mime]) return mimeLabels[mime]
|
||||
const name = (item.name || '').toLowerCase()
|
||||
const ext = name.split('.').pop()
|
||||
if (ext) return ext.toUpperCase()
|
||||
return 'Файл'
|
||||
return t('mime.file')
|
||||
}
|
||||
|
||||
export function getFileKind(item) {
|
||||
|
||||
@@ -245,6 +245,14 @@ export default {
|
||||
'error.nameEmpty': 'Имя не может быть пустым',
|
||||
'error.nameInvalid': 'Недопустимое имя',
|
||||
'error.selectCaseFirst': 'Сначала выберите дело',
|
||||
'common.open': 'Открыть',
|
||||
'delete.files': 'файлов ({count})',
|
||||
'file.namePrompt': 'Введите имя файла:',
|
||||
'file.pdfUnavailable': 'Предпросмотр PDF недоступен.',
|
||||
'file.previewUnavailable': 'Предпросмотр недоступен для этого типа файлов.',
|
||||
'case.new': 'Новое дело',
|
||||
'case.namePlaceholder': 'Название дела',
|
||||
|
||||
'error.generic': 'Произошла ошибка',
|
||||
'error.invalidCredentials': 'Неверный логин или пароль',
|
||||
'error.accountBlocked': 'Аккаунт заблокирован',
|
||||
|
||||
Reference in New Issue
Block a user