v0.3.2 — commit/submit progress, cancel, thumbnails, waitlist landing+admin

Client:
- Commit: batch `p4 reopen` so huge changelists (63k+) no longer blow the
  Windows command-line limit; live "N / total" progress + spinner during commit.
- Submit dialog: white arrow, "N of total" + % bar, running file log,
  Cancel button (aborts pre-commit; changelist stays pending).
- Disk scan: streaming reconcile with a live "found N files" counter + Cancel.
- Thumbnails: only decode browser-renderable image formats; glyph fallback
  (+ onError) instead of a broken-image icon for bmp/tga/dds/etc.

Landing + waitlist:
- waitlist-server: Express + SQLite email collector (dedup) with a secure
  admin panel (scrypt auth, rate-limited login, signed cookies) and CSV export.
- landing form now POSTs signups to the waitlist API.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Bonchellon
2026-07-08 20:40:53 +03:00
parent 5652f489a2
commit 3954082e8d
21 changed files with 3753 additions and 144 deletions

View File

@ -148,6 +148,11 @@ const D: Record<string, Tr> = {
"Save": { ru: "Сохранить", de: "Speichern", fr: "Enregistrer", es: "Guardar" },
"Scanning disk for changes…": { ru: "Ищу изменения на диске…", de: "Suche Änderungen auf der Festplatte…", fr: "Recherche de modifications sur le disque…", es: "Buscando cambios en el disco…" },
" for the whole workspace this is slow — pick a project folder above": { ru: " для всего workspace это долго — выбери рабочую папку проекта сверху", de: " für den ganzen Workspace ist das langsam — wähle oben einen Projektordner", fr: " pour tout le workspace cest lent — choisissez un dossier de projet en haut", es: " para todo el workspace es lento — elige una carpeta de proyecto arriba" },
"found {n} files": { ru: "найдено {n} файлов", de: "{n} Dateien gefunden", fr: "{n} fichiers trouvés", es: "{n} archivos encontrados" },
"Committing…": { ru: "Коммичу…", de: "Übertrage…", fr: "Validation…", es: "Confirmando…" },
"Cancelling…": { ru: "Отменяю…", de: "Breche ab…", fr: "Annulation…", es: "Cancelando…" },
"Stop before the server commit. Files already sent are not applied; the changelist stays pending.": { ru: "Остановить до серверного коммита. Уже отправленные файлы не применяются; changelist останется в pending.", de: "Vor dem Server-Commit stoppen. Bereits gesendete Dateien werden nicht übernommen; die Changelist bleibt ausstehend.", fr: "Arrêter avant le commit serveur. Les fichiers déjà envoyés ne sont pas appliqués ; la changelist reste en attente.", es: "Detener antes del commit del servidor. Los archivos ya enviados no se aplican; la changelist queda pendiente." },
"Stop the scan. Files already found stay staged.": { ru: "Остановить скан. Уже найденные файлы останутся помеченными.", de: "Scan stoppen. Bereits gefundene Dateien bleiben vorgemerkt.", fr: "Arrêter lanalyse. Les fichiers déjà trouvés restent préparés.", es: "Detener el escaneo. Los archivos ya encontrados quedan preparados." },
"{a} of {b} selected": { ru: "{a} из {b} выбрано", de: "{a} von {b} ausgewählt", fr: "{a} sur {b} sélectionnés", es: "{a} de {b} seleccionados" },
"Select all": { ru: "Выделить все", de: "Alle auswählen", fr: "Tout sélectionner", es: "Seleccionar todo" },
"Deselect all": { ru: "Снять выделение", de: "Auswahl aufheben", fr: "Tout désélectionner", es: "Deseleccionar todo" },
@ -539,6 +544,7 @@ const D: Record<string, Tr> = {
"Perforce's task/bug tracker — create jobs and attach them to changelists.": { ru: "Трекер задач/багов Perforce — создавай джобы и привязывай к changelists.", de: "Perforce-Aufgaben-/Bug-Tracker — Jobs erstellen und an Changelists anhängen.", fr: "Le suivi de tâches/bugs de Perforce — crée des jobs et attache-les aux changelists.", es: "El rastreador de tareas/errores de Perforce — crea jobs y adjúntalos a changelists." },
"Edit which files reconcile / add ignore (build output, caches).": { ru: "Изменить, какие файлы игнорируют reconcile / add (сборка, кэши).", de: "Bearbeiten, welche Dateien reconcile / add ignorieren (Build-Ausgabe, Caches).", fr: "Modifier quels fichiers reconcile / add ignorent (sortie de build, caches).", es: "Editar qué archivos ignoran reconcile / add (salida de compilación, cachés)." },
"Compile the Visual Studio solution found in the working folder.": { ru: "Скомпилировать решение Visual Studio, найденное в рабочей папке.", de: "Die im Arbeitsordner gefundene Visual-Studio-Solution kompilieren.", fr: "Compiler la solution Visual Studio trouvée dans le dossier de travail.", es: "Compilar la solución de Visual Studio encontrada en la carpeta de trabajo." },
"Compile the game C++ — Unreal projects build via UnrealBuildTool (game module only), plain C++ via MSBuild.": { ru: "Скомпилировать C++ игры — Unreal-проекты собираются через UnrealBuildTool (только модуль игры), обычный C++ — через MSBuild.", de: "Game-C++ kompilieren — Unreal-Projekte über UnrealBuildTool (nur Spielmodul), reines C++ über MSBuild.", fr: "Compiler le C++ du jeu — les projets Unreal via UnrealBuildTool (module de jeu seulement), le C++ simple via MSBuild.", es: "Compilar el C++ del juego — los proyectos Unreal vía UnrealBuildTool (solo el módulo del juego), C++ simple vía MSBuild." },
"See who works on this depot and their roles.": { ru: "Посмотреть, кто работает с этим депо и их роли.", de: "Sehen, wer an diesem Depot arbeitet und welche Rollen sie haben.", fr: "Voir qui travaille sur ce depot et leurs rôles.", es: "Ver quién trabaja en este depot y sus roles." },
"App preferences — language, theme, editor, and more.": { ru: "Настройки приложения — язык, тема, редактор и прочее.", de: "App-Einstellungen — Sprache, Design, Editor und mehr.", fr: "Préférences de l'app — langue, thème, éditeur et plus.", es: "Preferencias de la app — idioma, tema, editor y más." },
"Version and information about Exbyte Depot.": { ru: "Версия и информация о Exbyte Depot.", de: "Version und Informationen zu Exbyte Depot.", fr: "Version et informations sur Exbyte Depot.", es: "Versión e información sobre Exbyte Depot." },
@ -575,6 +581,34 @@ const D: Record<string, Tr> = {
"Duration": { ru: "Длительность", de: "Dauer", fr: "Durée", es: "Duración" },
"server revision": { ru: "ревизия с сервера", de: "Server-Revision", fr: "révision serveur", es: "revisión del servidor" },
"Everyone's checked-out / exclusively-locked files, docked into the view panel.": { ru: "Все чекнутые / эксклюзивно залоченные файлы всех участников, встроенные в панель просмотра.", de: "Alle ausgecheckten / exklusiv gesperrten Dateien aller Nutzer, angedockt im Ansichts-Panel.", fr: "Les fichiers extraits / verrouillés en exclusivité de tout le monde, ancrés dans le panneau.", es: "Los archivos extraídos / bloqueados en exclusiva de todos, anclados en el panel." },
// ---- new folder / project ----
"New": { ru: "Создать", de: "Neu", fr: "Nouveau", es: "Nuevo" },
"workspace root": { ru: "корень воркспейса", de: "Arbeitsbereich-Root", fr: "racine de l'espace de travail", es: "raíz del espacio de trabajo" },
"New folder / project…": { ru: "Новая папка / проект…", de: "Neuer Ordner / Projekt…", fr: "Nouveau dossier / projet…", es: "Nueva carpeta / proyecto…" },
"Create a new folder in the working area (with a README placeholder) — e.g. a separate project.": { ru: "Создать новую папку в рабочей области (с плейсхолдером README) — например, отдельный проект.", de: "Einen neuen Ordner im Arbeitsbereich erstellen (mit README-Platzhalter) — z. B. ein separates Projekt.", fr: "Créer un nouveau dossier dans l'espace de travail (avec un README) — p. ex. un projet distinct.", es: "Crear una nueva carpeta en el área de trabajo (con un README) — p. ej. un proyecto aparte." },
"New folder in {where}": { ru: "Новая папка в {where}", de: "Neuer Ordner in {where}", fr: "Nouveau dossier dans {where}", es: "Nueva carpeta en {where}" },
"Folder name — nest with / (e.g. NewGame or NewGame/Source)": { ru: "Имя папки — вложенность через / (напр. NewGame или NewGame/Source)", de: "Ordnername — verschachteln mit / (z. B. NewGame oder NewGame/Source)", fr: "Nom du dossier — imbriquer avec / (p. ex. NewGame ou NewGame/Source)", es: "Nombre de la carpeta — anida con / (p. ej. NewGame o NewGame/Source)" },
"Created “{name}” with a README placeholder — commit it to keep the folder on the server.": { ru: "Создано «{name}» с плейсхолдером README — закоммить, чтобы папка сохранилась на сервере.", de: "„{name}“ mit README-Platzhalter erstellt — committe es, damit der Ordner auf dem Server bleibt.", fr: "« {name} » créé avec un README — committe-le pour conserver le dossier sur le serveur.", es: "«{name}» creado con un README — haz commit para conservar la carpeta en el servidor." },
// ---- rename / delete / properties folder ----
"Rename…": { ru: "Переименовать…", de: "Umbenennen…", fr: "Renommer…", es: "Renombrar…" },
"Rename folder": { ru: "Переименовать папку", de: "Ordner umbenennen", fr: "Renommer le dossier", es: "Renombrar carpeta" },
"New name for “{name}”": { ru: "Новое имя для «{name}»", de: "Neuer Name für „{name}“", fr: "Nouveau nom pour « {name} »", es: "Nuevo nombre para «{name}»" },
"Renamed to “{name}” — {n} file(s) moved. Commit to apply.": { ru: "Переименовано в «{name}» — перемещено {n} файл(ов). Закоммить, чтобы применить.", de: "In „{name}“ umbenannt — {n} Datei(en) verschoben. Zum Anwenden committen.", fr: "Renommé en « {name} » — {n} fichier(s) déplacés. Committe pour appliquer.", es: "Renombrado a «{name}» — {n} archivo(s) movidos. Haz commit para aplicar." },
"Delete folder…": { ru: "Удалить папку…", de: "Ordner löschen…", fr: "Supprimer le dossier…", es: "Eliminar carpeta…" },
"Delete folder “{name}”?": { ru: "Удалить папку «{name}»?", de: "Ordner „{name}“ löschen?", fr: "Supprimer le dossier « {name} » ?", es: "¿Eliminar la carpeta «{name}»?" },
"Every file under this folder is marked for delete. Nothing leaves the server until you Submit; freshly-added files are removed right away.": { ru: "Все файлы под этой папкой помечаются на удаление. С сервера ничего не уходит до Submit; свежедобавленные файлы убираются сразу.", de: "Jede Datei unter diesem Ordner wird zum Löschen markiert. Nichts verlässt den Server bis zum Submit; frisch hinzugefügte Dateien werden sofort entfernt.", fr: "Chaque fichier de ce dossier est marqué pour suppression. Rien ne quitte le serveur avant le Submit ; les fichiers récemment ajoutés sont retirés immédiatement.", es: "Todos los archivos de esta carpeta se marcan para eliminar. Nada sale del servidor hasta el Submit; los archivos recién añadidos se quitan al instante." },
"Marked {n} file(s) for delete — Submit to remove the folder.": { ru: "Помечено {n} файл(ов) на удаление — Submit уберёт папку.", de: "{n} Datei(en) zum Löschen markiert — Submit entfernt den Ordner.", fr: "{n} fichier(s) marqués pour suppression — Submit retire le dossier.", es: "{n} archivo(s) marcados para eliminar — Submit quita la carpeta." },
"Folder removed.": { ru: "Папка удалена.", de: "Ordner entfernt.", fr: "Dossier supprimé.", es: "Carpeta eliminada." },
"Delete": { ru: "Удалить", de: "Löschen", fr: "Supprimer", es: "Eliminar" },
"Properties": { ru: "Свойства", de: "Eigenschaften", fr: "Propriétés", es: "Propiedades" },
"Folder properties": { ru: "Свойства папки", de: "Ordner-Eigenschaften", fr: "Propriétés du dossier", es: "Propiedades de la carpeta" },
"Files": { ru: "Файлов", de: "Dateien", fr: "Fichiers", es: "Archivos" },
"Total size": { ru: "Общий размер", de: "Gesamtgröße", fr: "Taille totale", es: "Tamaño total" },
"Location": { ru: "Расположение", de: "Speicherort", fr: "Emplacement", es: "Ubicación" },
"depot": { ru: "депо", de: "Depot", fr: "depot", es: "depot" },
"local only": { ru: "только локально", de: "nur lokal", fr: "local uniquement", es: "solo local" },
"Recently": { ru: "Недавние", de: "Zuletzt", fr: "Récents", es: "Recientes" },
"local": { ru: "локально", de: "lokal", fr: "local", es: "local" },
};
export function t(en: string, vars?: Record<string, string | number>): string {