fix: resolve Auto-Trim canvas CORS block with webSecurity, implement dynamic multi-language localization (EN, TR, DE, RU, AR) with English base

This commit is contained in:
Ümit Tunç
2026-05-23 11:01:25 +03:00
parent dba9ac7642
commit 868d7519a4
3 changed files with 406 additions and 56 deletions
+46 -36
View File
@@ -41,6 +41,16 @@
<span class="badge">Trunçgil AI</span>
</div>
<div class="titlebar-controls">
<!-- Language Switcher Selector -->
<div class="select-wrapper language-select-wrapper" style="width: 76px; height: 26px; margin-right: 8px; -webkit-app-region: no-drag;">
<select id="lang-select" style="padding: 2px 20px 2px 8px; font-size: 11px; height: 100%; border-radius: 6px; background: rgba(255,255,255,0.04); border-color: rgba(255,255,255,0.08); font-weight: 600;">
<option value="en">EN</option>
<option value="tr">TR</option>
<option value="de">DE</option>
<option value="ru">RU</option>
<option value="ar">AR</option>
</select>
</div>
<button class="win-btn info-btn" id="btn-show-credits" title="Credits & Info">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
</button>
@@ -77,27 +87,27 @@
<circle cx="12" cy="12" r="4" fill="url(#neon-grad)" opacity="0.3" />
</svg>
</div>
<h1>Initializing AI Engine</h1>
<p class="subtitle">Dekupai requires a local Python environment to run the BiRefNet neural network models.</p>
<h1 data-i18n="initWizardTitle">Initializing AI Engine</h1>
<p class="subtitle" data-i18n="initWizardSubtitle">Dekupai requires a local Python environment to run the BiRefNet neural network models.</p>
</div>
<div class="setup-progress-box glass-panel-inner">
<div class="setup-steps">
<div class="step-item" id="step-venv">
<span class="step-dot"></span>
<span class="step-text">Creating isolated virtual environment (.venv)</span>
<span class="step-text" data-i18n="stepVenv">Creating isolated virtual environment (.venv)</span>
</div>
<div class="step-item" id="step-pip">
<span class="step-dot"></span>
<span class="step-text">Upgrading Python package installer (pip)</span>
<span class="step-text" data-i18n="stepPip">Upgrading Python package installer (pip)</span>
</div>
<div class="step-item" id="step-deps">
<span class="step-dot"></span>
<span class="step-text">Installing PyTorch AI Engine (CPU version)</span>
<span class="step-text" data-i18n="stepDeps">Installing PyTorch AI Engine (CPU version)</span>
</div>
<div class="step-item" id="step-libs">
<span class="step-dot"></span>
<span class="step-text">Installing HuggingFace Transformers & PIL library</span>
<span class="step-text" data-i18n="stepLibs">Installing HuggingFace Transformers & PIL library</span>
</div>
</div>
@@ -109,7 +119,7 @@
<div class="wizard-actions">
<button class="btn btn-primary" id="btn-start-setup">
<span>Configure Engine (Automatic)</span>
<span data-i18n="configureEngine">Configure Engine (Automatic)</span>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 18 15 12 9 6"></polyline>
@@ -134,11 +144,11 @@
<polyline points="21 15 16 10 5 21"></polyline>
</svg>
</div>
<h2>Drag & Drop Image Here</h2>
<p class="dropzone-sub">Supports PNG, JPG, JPEG, or WebP formats</p>
<div class="or-separator"><span>OR</span></div>
<h2 data-i18n="dragDropText">Drag & Drop Image Here</h2>
<p class="dropzone-sub" data-i18n="dragDropSub">Supports PNG, JPG, JPEG, or WebP formats</p>
<div class="or-separator"><span data-i18n="orText">OR</span></div>
<button class="btn btn-glass" id="btn-browse">
<span>Browse Local Files</span>
<span data-i18n="browseFiles">Browse Local Files</span>
</button>
</div>
</div>
@@ -215,7 +225,7 @@
<path d="M4 3h16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"></path>
<path d="M8 8h8v8H8z"></path>
</svg>
<span>Auto-Trim</span>
<span data-i18n="autoTrim">Auto-Trim</span>
</button>
<button class="ctrl-btn btn-danger-icon" id="ctrl-reset" title="Reset Image State">
@@ -234,27 +244,27 @@
<section class="tower-group" id="sec-file-info">
<div class="tower-heading">
<span class="step-badge">1</span>
<h3>Source File</h3>
<h3 data-i18n="sourceFile">Source File</h3>
</div>
<div class="info-card glass-panel-inner" id="info-card-empty">
<p class="text-muted text-center py-2">No image loaded</p>
<p class="text-muted text-center py-2" data-i18n="noImage">No image loaded</p>
</div>
<div class="info-card glass-panel-inner hidden" id="info-card-loaded">
<div class="info-row">
<span class="label">Name</span>
<span class="label" data-i18n="fileName">Name</span>
<span class="val truncate" id="info-filename">-</span>
</div>
<div class="info-row">
<span class="label">Resolution</span>
<span class="label" data-i18n="fileResolution">Resolution</span>
<span class="val" id="info-resolution">-</span>
</div>
<div class="info-row">
<span class="label">Size</span>
<span class="label" data-i18n="fileSize">Size</span>
<span class="val" id="info-size">-</span>
</div>
<button class="btn btn-secondary btn-sm w-full" id="btn-change-image" style="margin-top: 10px; border-color: rgba(255, 122, 0, 0.2) !important;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>
<span style="font-size: 11px;">Yeni Görsel Yükle</span>
<span style="font-size: 11px;" data-i18n="newImage">Import New Image</span>
</button>
</div>
</section>
@@ -263,7 +273,7 @@
<section class="tower-group" id="sec-ai-process">
<div class="tower-heading">
<span class="step-badge">2</span>
<h3>Background Removal</h3>
<h3 data-i18n="bgRemoval">Background Removal</h3>
</div>
<button class="btn btn-primary w-full" id="btn-remove-bg" disabled>
@@ -271,7 +281,7 @@
stroke-linecap="round" stroke-linejoin="round">
<path d="M21 16V8a2 2 0 0 0-2-2h-5m-9 0h5M3 8v8a2 2 0 0 0 2 2h8m9 0h-5"></path>
</svg>
<span>Extract Subject</span>
<span data-i18n="extractSubject">Extract Subject</span>
</button>
<!-- Loader panel inside -->
@@ -282,8 +292,8 @@
<circle cx="25" cy="25" r="20" fill="none" stroke-width="4"></circle>
</svg>
</div>
<div class="loader-text" id="loader-text">Initializing AI Model...</div>
<div class="loader-subtext" id="loader-subtext">First launch downloads model (~200MB)</div>
<div class="loader-text" id="loader-text" data-i18n="initAI">Initializing AI Model...</div>
<div class="loader-subtext" id="loader-subtext" data-i18n="loadingWeights">First launch downloads model (~200MB)</div>
</div>
</section>
@@ -291,24 +301,24 @@
<section class="tower-group disabled-group" id="sec-canvas-bg">
<div class="tower-heading">
<span class="step-badge">3</span>
<h3>Background Settings</h3>
<h3 data-i18n="bgSettings">Background Settings</h3>
</div>
<div class="bg-mode-tabs glass-panel-inner">
<button class="bg-tab active" data-mode="transparent" id="tab-bg-trans">
<span>Transparent</span>
<span data-i18n="transparent">Transparent</span>
</button>
<button class="bg-tab" data-mode="solid" id="tab-bg-solid">
<span>Solid Color</span>
<span data-i18n="solidColor">Solid Color</span>
</button>
<button class="bg-tab" data-mode="gradient" id="tab-bg-grad">
<span>Gradient</span>
<span data-i18n="gradient">Gradient</span>
</button>
</div>
<!-- Solid Colors Panel -->
<div class="bg-settings-subpanel hidden" id="subpanel-solid">
<label class="panel-sublabel">Select Solid Fill</label>
<label class="panel-sublabel" data-i18n="selectSolid">Select Solid Fill</label>
<div class="color-swatches-grid">
<button class="swatch white active" data-color="#ffffff"></button>
<button class="swatch black" data-color="#000000"></button>
@@ -328,7 +338,7 @@
<!-- Gradients Panel -->
<div class="bg-settings-subpanel hidden" id="subpanel-gradient">
<label class="panel-sublabel">Select High-End Gradient</label>
<label class="panel-sublabel" data-i18n="selectGrad">Select High-End Gradient</label>
<div class="gradient-swatches-grid">
<button class="grad-swatch grad-1 active" data-gradient="linear-gradient(135deg, #a78bfa, #06b6d4)"
title="Violet Cyan Glow"></button>
@@ -350,12 +360,12 @@
<section class="tower-group disabled-group" id="sec-export">
<div class="tower-heading">
<span class="step-badge">4</span>
<h3>Export Studio</h3>
<h3 data-i18n="exportStudio">Export Studio</h3>
</div>
<div class="export-settings-box glass-panel-inner">
<div class="settings-row">
<label for="export-format">Format</label>
<label for="export-format" data-i18n="format">Format</label>
<div class="select-wrapper">
<select id="export-format">
<option value="png">PNG (Lossless)</option>
@@ -367,7 +377,7 @@
<div class="settings-row hidden" id="row-quality-slider">
<div class="slider-labels">
<label for="export-quality">Quality</label>
<label for="export-quality" data-i18n="quality">Quality</label>
<span class="slider-val" id="export-quality-val">90%</span>
</div>
<input type="range" id="export-quality" min="10" max="100" value="90">
@@ -381,7 +391,7 @@
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
<span>Export Masterpiece</span>
<span data-i18n="exportMasterpiece">Export Masterpiece</span>
</button>
</section>
</aside>
@@ -412,20 +422,20 @@
<div style="font-size: 11px; font-weight: 700; color: #ff7a00; letter-spacing: 1.5px; margin-bottom: 12px; text-transform: uppercase;">Version 1.0.0</div>
<div class="glass-panel-inner" style="margin-top: 14px; text-align: center; display: flex; flex-direction: column; gap: 10px; padding: 20px !important;">
<p style="font-size: 13px; color: var(--text-muted); font-weight: 500;">Developed under the vision of</p>
<p style="font-size: 13px; color: var(--text-muted); font-weight: 500;" data-i18n="creditsVision">Developed under the vision of</p>
<h2 style="font-family: var(--font-heading); font-size: 22px; font-weight: 800; color: #ff7a00; text-shadow: 0 0 12px rgba(255, 122, 0, 0.35);">Trunçgil Teknoloji</h2>
<p style="font-size: 12px; color: var(--text-muted-dark); line-height: 1.4;">by the engineering expertise of</p>
<p style="font-size: 12px; color: var(--text-muted-dark); line-height: 1.4;" data-i18n="creditsExpertise">by the engineering expertise of</p>
<h3 style="font-family: var(--font-heading); font-size: 19px; font-weight: 700; color: #fff;">Ümit Tunç</h3>
</div>
<div style="margin-top: 16px; font-size: 12px; color: var(--text-muted-dark); line-height: 1.5; padding: 0 10px;">
<div style="margin-top: 16px; font-size: 12px; color: var(--text-muted-dark); line-height: 1.5; padding: 0 10px;" data-i18n="creditsDesc">
Dekupai is an advanced deep-learning desktop workspace that provides local, high-precision background removal using BiRefNet neural segmentation networks.
</div>
</div>
<div class="wizard-actions" style="margin-top: 10px; width: 100%;">
<button class="btn btn-secondary w-full" id="btn-close-credits">
<span>Close Credits</span>
<span data-i18n="closeCredits">Close Credits</span>
</button>
</div>
</div>
+2 -1
View File
@@ -17,7 +17,8 @@ function createWindow() {
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
nodeIntegration: false,
webSecurity: false // Disabled to allow canvas pixel reading on local file:// assets for Auto-Trim
}
});
+358 -19
View File
@@ -33,6 +33,7 @@ const elements = {
btnShowCredits: document.getElementById('btn-show-credits'),
creditsOverlay: document.getElementById('credits-overlay'),
btnCloseCredits: document.getElementById('btn-close-credits'),
langSelect: document.getElementById('lang-select'),
// Setup Wizard
setupOverlay: document.getElementById('setup-overlay'),
@@ -106,6 +107,7 @@ const elements = {
document.addEventListener('DOMContentLoaded', async () => {
setupTitlebarListeners();
setupToastListeners();
initLanguage(); // Initialize translations immediately on load
// Check if Python virtual environment exists
const { hasVenv } = await window.api.checkPythonSetup();
@@ -424,18 +426,25 @@ function setupToolbarListeners() {
function autoTrimTransparent() {
if (!cropper || !processedTempPath) return;
// We request canvas size matching the full uncropped image dimension
const canvas = cropper.getCroppedCanvas({
width: cropper.getImageData().naturalWidth,
height: cropper.getImageData().naturalHeight
});
// Create a temporary hidden canvas to read pixels directly from the original image element
// This bypasses any cropper canvas restrictions or CORS issues when webSecurity is false
const canvas = document.createElement('canvas');
const img = elements.imageElement;
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext('2d');
if (!canvas) {
showToast("Trim Warning", "Could not analyze transparent boundaries.", "error");
try {
ctx.drawImage(img, 0, 0);
} catch (err) {
const title = getTrans('toastTrimFailed');
const msg = getTrans('toastTrimFailedMsg') + ": " + err.message;
showToast(title, msg, "error");
return;
}
const ctx = canvas.getContext('2d');
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imgData.data;
@@ -445,11 +454,11 @@ function autoTrimTransparent() {
let maxY = 0;
let foundAlpha = false;
// Loop through pixels in grid to find bounding box of non-transparent values
// Scan alpha channel (index + 3) to locate non-empty boundaries
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
const alphaIndex = (y * canvas.width + x) * 4 + 3;
if (data[alphaIndex] > 8) { // Alpha transparency cut-off
if (data[alphaIndex] > 8) { // transparency threshold
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
@@ -460,16 +469,21 @@ function autoTrimTransparent() {
}
if (foundAlpha) {
// Add small padding to avoid clipping exactly
// Add small padding to avoid clipping edge pixels exactly
const pad = 10;
minX = Math.max(0, minX - pad);
minY = Math.max(0, minY - pad);
maxX = Math.min(canvas.width, maxX + pad);
maxY = Math.min(canvas.height, maxY + pad);
// Turn on Crop selection mode visual indicators
cropper.setDragMode('crop');
elements.ctrlCropDrag.classList.remove('active');
elements.ctrlCropBox.classList.add('active');
// Convert pixel coordinates to Cropper canvas boundaries
const canvasData = cropper.getCanvasData();
const scale = canvasData.width / cropper.getImageData().naturalWidth;
const scale = canvasData.width / img.naturalWidth;
// Set crop boundaries
cropper.setCropBoxData({
@@ -479,17 +493,342 @@ function autoTrimTransparent() {
height: (maxY - minY) * scale
});
// Switch to crop selection mode visual indicator
cropper.setDragMode('crop');
elements.ctrlCropDrag.classList.remove('active');
elements.ctrlCropBox.classList.add('active');
showToast("Auto-Trim Complete", "Crop boundaries adjusted securely around subject.", "success");
showToast(getTrans('toastTrimComplete'), getTrans('toastTrimCompleteMsg'), "success");
} else {
showToast("Trim Info", "No transparent border boundaries identified.", "info");
showToast(getTrans('toastTitleInfo'), getTrans('toastTrimFailedMsg'), "info");
}
}
// --- LOCALIZATION ENGINE (i18n) ---
const TRANSLATIONS = {
en: {
title: "Dekupai - Trunçgil AI Decoupage",
sourceFile: "Source File",
noImage: "No image loaded",
fileName: "Name",
fileResolution: "Resolution",
fileSize: "Size",
newImage: "Import New Image",
bgRemoval: "Background Removal",
extractSubject: "Extract Subject",
initAI: "Initializing AI Model...",
loadingWeights: "First launch downloads model (~200MB)",
bgSettings: "Background Settings",
transparent: "Transparent",
solidColor: "Solid Color",
gradient: "Gradient",
selectSolid: "Select Solid Fill",
selectGrad: "Select High-End Gradient",
exportStudio: "Export Studio",
format: "Format",
quality: "Quality",
exportMasterpiece: "Export Masterpiece",
toastTitleInfo: "Info",
toastTitleSuccess: "Success",
toastTitleError: "Error",
toastImageChanged: "Image Changed",
toastImageChangedMsg: "Workspace cleared. Ready for a new image.",
toastTrimComplete: "Auto-Trim Complete",
toastTrimCompleteMsg: "Crop boundaries adjusted securely.",
toastTrimFailed: "Trim Warning",
toastTrimFailedMsg: "Could not analyze transparent boundaries.",
toastSetupSuccess: "Setup Success",
toastSetupSuccessMsg: "Python environment is fully configured.",
toastSetupError: "Setup Error",
toastSetupErrorMsg: "Failed to configure Python environment.",
initWizardTitle: "Initializing AI Engine",
initWizardSubtitle: "Dekupai requires a local Python environment to run the BiRefNet neural network models.",
configureEngine: "Configure Engine (Automatic)",
stepVenv: "Creating isolated virtual environment (.venv)",
stepPip: "Upgrading Python package installer (pip)",
stepDeps: "Installing PyTorch AI Engine (CPU version)",
stepLibs: "Installing HuggingFace Transformers & PIL library",
creditsVision: "Developed under the vision of",
creditsExpertise: "by the engineering expertise of",
creditsDesc: "Dekupai is an advanced deep-learning desktop workspace that provides local, high-precision background removal using BiRefNet neural segmentation networks.",
closeCredits: "Close Credits",
dragDropText: "Drag & Drop Image Here",
dragDropSub: "Supports PNG, JPG, JPEG, or WebP formats",
orText: "OR",
browseFiles: "Browse Local Files",
autoTrim: "Auto-Trim"
},
tr: {
title: "Dekupai - Trunçgil AI Dekupaj",
sourceFile: "Kaynak Dosya",
noImage: "Görsel yüklenmedi",
fileName: "Dosya Adı",
fileResolution: "Çözünürlük",
fileSize: "Boyut",
newImage: "Yeni Görsel Yükle",
bgRemoval: "Arka Plan Temizleme",
extractSubject: "Özneyi Dekupe Et",
initAI: "AI Modeli Başlatılıyor...",
loadingWeights: "İlk çalıştırmada model indirilir (~200MB)",
bgSettings: "Arka Plan Ayarları",
transparent: "Şeffaf",
solidColor: "Tek Renk",
gradient: "Degrade",
selectSolid: "Düz Renk Seçin",
selectGrad: "Premium Degrade Seçin",
exportStudio: "Dışa Aktarma Stüdyosu",
format: "Format",
quality: "Kalite",
exportMasterpiece: "Çalışmayı Dışa Aktar",
toastTitleInfo: "Bilgi",
toastTitleSuccess: "Başarılı",
toastTitleError: "Hata",
toastImageChanged: "Görsel Değiştirildi",
toastImageChangedMsg: "Çalışma alanı temizlendi. Yeni görsel için hazır.",
toastTrimComplete: "Otomatik Kırpma Tamamlandı",
toastTrimCompleteMsg: "Kırpma sınırları güvenli bir şekilde ayarlandı.",
toastTrimFailed: "Kırpma Uyarısı",
toastTrimFailedMsg: "Şeffaf sınırlar analiz edilemedi.",
toastSetupSuccess: "Kurulum Başarılı",
toastSetupSuccessMsg: "Python ortamı tamamen yapılandırıldı.",
toastSetupError: "Kurulum Hatası",
toastSetupErrorMsg: "Python ortamı yapılandırılamadı.",
initWizardTitle: "AI Motoru Başlatılıyor",
initWizardSubtitle: "Dekupai, BiRefNet yapay zeka modelini çalıştırmak için yerel bir Python ortamına ihtiyaç duyar.",
configureEngine: "Motoru Yapılandır (Otomatik)",
stepVenv: "İzole sanal ortam oluşturuluyor (.venv)",
stepPip: "Python paket yöneticisi güncelleniyor (pip)",
stepDeps: "PyTorch AI motoru kuruluyor (CPU versiyonu)",
stepLibs: "HuggingFace Transformers ve PIL kuruluyor",
creditsVision: "Vizyon ve liderlik",
creditsExpertise: "yazılım geliştirme ve mühendislik",
creditsDesc: "Dekupai, BiRefNet yapay sinir ağı segmentasyon modellerini kullanarak yerel ve yüksek hassasiyetli arka plan temizleme sağlayan gelişmiş bir derin öğrenme masaüstü uygulamasıdır.",
closeCredits: "Kapat",
dragDropText: "Görseli Buraya Sürükleyip Bırakın",
dragDropSub: "PNG, JPG, JPEG veya WebP formatlarını destekler",
orText: "VEYA",
browseFiles: "Yerel Dosyalara Göz Atın",
autoTrim: "Oto-Kırp"
},
de: {
title: "Dekupai - Trunçgil AI Dekupage",
sourceFile: "Quelldatei",
noImage: "Kein Bild geladen",
fileName: "Dateiname",
fileResolution: "Auflösung",
fileSize: "Größe",
newImage: "Neues Bild Laden",
bgRemoval: "Hintergrundentfernung",
extractSubject: "Motiv Freistellen",
initAI: "AI-Modell wird initialisiert...",
loadingWeights: "Erster Start lädt Modell herunter (~200MB)",
bgSettings: "Hintergrundeinstellungen",
transparent: "Transparent",
solidColor: "Vollfarbe",
gradient: "Verlauf",
selectSolid: "Volltonfarbe wählen",
selectGrad: "Premium-Verlauf wählen",
exportStudio: "Export Studio",
format: "Format",
quality: "Qualität",
exportMasterpiece: "Meisterwerk Exportieren",
toastTitleInfo: "Info",
toastTitleSuccess: "Erfolg",
toastTitleError: "Fehler",
toastImageChanged: "Bild Geändert",
toastImageChangedMsg: "Arbeitsbereich geleert. Bereit für neues Bild.",
toastTrimComplete: "Auto-Zuschneiden Fertig",
toastTrimCompleteMsg: "Zuschnittsgrenzen sicher angepasst.",
toastTrimFailed: "Zuschnitt Warnung",
toastTrimFailedMsg: "Transparente Grenzen konnten nicht analysiert werden.",
toastSetupSuccess: "Setup erfolgreich",
toastSetupSuccessMsg: "Python-Umgebung ist vollständig konfiguriert.",
toastSetupError: "Setup-Fehler",
toastSetupErrorMsg: "Konfiguration der Python-Umgebung fehlgeschlagen.",
initWizardTitle: "AI-Engine wird initialisiert",
initWizardSubtitle: "Dekupai erfordert eine lokale Python-Umgebung, um die BiRefNet-Modelle auszuführen.",
configureEngine: "Engine konfigurieren (Automatisch)",
stepVenv: "Erstellen einer isolierten virtuellen Umgebung (.venv)",
stepPip: "Upgrade des Python-Paketinstallers (pip)",
stepDeps: "Installation der PyTorch AI-Engine (CPU-Version)",
stepLibs: "Installation der HuggingFace Transformers & PIL Bibliothek",
creditsVision: "Entwickelt unter der Vision von",
creditsExpertise: "durch die Software-Expertise von",
creditsDesc: "Dekupai ist ein fortschrittlicher Deep-Learning-Desktop-Arbeitsbereich, der eine lokale, hochpräzise Hintergrundentfernung mithilfe von BiRefNet-Segmentierungsnetzwerken bietet.",
closeCredits: "Schließen",
dragDropText: "Bild hierher ziehen und ablegen",
dragDropSub: "Unterstützt PNG-, JPG-, JPEG- oder WebP-Formate",
orText: "ODER",
browseFiles: "Lokale Dateien durchsuchen",
autoTrim: "Auto-Trim"
},
ru: {
title: "Dekupai - Trunçgil AI Декупаж",
sourceFile: "Исходный файл",
noImage: "Изображение не загружено",
fileName: "Имя",
fileResolution: "Разрешение",
fileSize: "Размер",
newImage: "Загрузить новое изображение",
bgRemoval: "Удаление фона",
extractSubject: "Вырезать объект",
initAI: "Инициализация модели ИИ...",
loadingWeights: "Первый запуск загружает модель (~200 МБ)",
bgSettings: "Настройки фона",
transparent: "Прозрачный",
solidColor: "Сплошной цвет",
gradient: "Градиент",
selectSolid: "Выберите сплошной цвет",
selectGrad: "Выберите премиум градиент",
exportStudio: "Студия экспорта",
format: "Формат",
quality: "Качество",
exportMasterpiece: "Экспортировать шедевр",
toastTitleInfo: "Информация",
toastTitleSuccess: "Успех",
toastTitleError: "Ошибка",
toastImageChanged: "Изображение изменено",
toastImageChangedMsg: "Рабочая область очищена. Готово к новому изображению.",
toastTrimComplete: "Автообрезка завершена",
toastTrimCompleteMsg: "Границы обрезки успешно скорректированы.",
toastTrimFailed: "Предупреждение об обрезке",
toastTrimFailedMsg: "Не удалось проанализировать прозрачные границы.",
toastSetupSuccess: "Установка успешна",
toastSetupSuccessMsg: "Среда Python полностью настроена.",
toastSetupError: "Ошибка установки",
toastSetupErrorMsg: "Не удалось настроить среду Python.",
initWizardTitle: "Инициализация движка ИИ",
initWizardSubtitle: "Для работы Dekupai требуется локальная среда Python для запуска нейросетевых моделей BiRefNet.",
configureEngine: "Настроить движок (Автоматически)",
stepVenv: "Создание изолированной виртуальной среды (.venv)",
stepPip: "Обновление установщика пакетов Python (pip)",
stepDeps: "Установка движка ИИ PyTorch (версия для CPU)",
stepLibs: "Установка библиотек HuggingFace Transformers и PIL",
creditsVision: "Разработано под руководством",
creditsExpertise: "программная инженерия и разработка",
creditsDesc: "Dekupai — это продвинутое настольное приложение для глубокого обучения, обеспечивающее локальное высокоточное удаление фона с использованием моделей сегментации BiRefNet.",
closeCredits: "Закрыть",
dragDropText: "Перетащите изображение сюда",
dragDropSub: "Поддерживает форматы PNG, JPG, JPEG или WebP",
orText: "ИЛИ",
browseFiles: "Обзор локальных файлов",
autoTrim: "Автообрезка"
},
ar: {
title: "Dekupai - Trunçgil AI إزالة الخلفية",
sourceFile: "الملف المصدر",
noImage: "لم يتم تحميل أي صورة",
fileName: "الاسم",
fileResolution: "الدقة",
fileSize: "الحجم",
newImage: "تحميل صورة جديدة",
bgRemoval: "إزالة الخلفية",
extractSubject: "استخراج العنصر",
initAI: "تهيئة نموذج الذكاء الاصطناعي...",
loadingWeights: "التشغيل الأول يقوم بتحميل النموذج (~200 ميجابايت)",
bgSettings: "إعدادات الخلفية",
transparent: "شفاف",
solidColor: "لون موحد",
gradient: "تدرج لوني",
selectSolid: "اختر لوناً موحداً",
selectGrad: "اختر تدرجاً لونيًا فاخرًا",
exportStudio: "استوديو التصدير",
format: "الصيغة",
quality: "الجودة",
exportMasterpiece: "تصدير العمل الفني",
toastTitleInfo: "معلومات",
toastTitleSuccess: "نجاح",
toastTitleError: "خطأ",
toastImageChanged: "تم تغيير الصورة",
toastImageChangedMsg: "تم مسح مساحة العمل. جاهز لصورة جديدة.",
toastTrimComplete: "اكتمل القص التلقائي",
toastTrimCompleteMsg: "تم ضبط حدود القص بأمان حول العنصر.",
toastTrimFailed: "تحذير القص",
toastTrimFailedMsg: "تعذر تحليل الحدود الشفافة.",
toastSetupSuccess: "نجاح التهيئة",
toastSetupSuccessMsg: "تم تكوين بيئة بايثون بنجاح.",
toastSetupError: "خطأ في التهيئة",
toastSetupErrorMsg: "فشل تكوين بيئة بايثون.",
initWizardTitle: "تهيئة محرك الذكاء الاصطناعي",
initWizardSubtitle: "يتطلب Dekupai بيئة بايثون محلية لتشغيل نماذج الشبكة العصبية BiRefNet.",
configureEngine: "تكوين المحرك (تلقائي)",
stepVenv: "إنشاء بيئة افتراضية معزولة (.venv)",
stepPip: "ترقية مثبت حزم بايثون (pip)",
stepDeps: "تثبيت محرك الذكاء الاصطناعي PyTorch (نسخة المعالج CPU)",
stepLibs: "تثبيت مكتبة HuggingFace Transformers و PIL",
creditsVision: "تم التطوير تحت رؤية",
creditsExpertise: "بواسطة الخبرة البرمجية والهندسية لـ",
creditsDesc: "Dekupai هو مساحة عمل متقدمة للتعلم العميق توفر إزالة محلية ودقيقة للغاية للخلفيات باستخدام نماذج BiRefNet.",
closeCredits: "إغلاق",
dragDropText: "اسحب وأسقط الصورة هنا",
dragDropSub: "يدعم صيغ PNG أو JPG أو JPEG أو WebP",
orText: "أو",
browseFiles: "تصفح الملفات المحلية",
autoTrim: "قص تلقائي"
}
};
let currentLang = 'en'; // default English base language
function changeLanguage(lang) {
currentLang = lang;
localStorage.setItem('dekupai_lang', lang);
if (elements.langSelect) {
elements.langSelect.value = lang;
}
// Update HTML elements marked with data-i18n
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
if (TRANSLATIONS[lang] && TRANSLATIONS[lang][key]) {
// If element contains an inner span for icon text
const span = el.querySelector('span');
if (span) {
span.innerText = TRANSLATIONS[lang][key];
} else if (el.tagName === 'OPTION') {
el.innerText = TRANSLATIONS[lang][key];
} else {
// Preserving SVG structure inside tags
const svg = el.querySelector('svg');
if (svg) {
// If it has SVG, clear other text and append text node safely
const textNodes = Array.from(el.childNodes).filter(node => node.nodeType === Node.TEXT_NODE);
if (textNodes.length > 0) {
textNodes[textNodes.length - 1].nodeValue = " " + TRANSLATIONS[lang][key];
} else {
el.appendChild(document.createTextNode(" " + TRANSLATIONS[lang][key]));
}
} else {
el.innerText = TRANSLATIONS[lang][key];
}
}
}
});
// Handle RTL for Arabic
if (lang === 'ar') {
document.body.dir = 'rtl';
} else {
document.body.dir = 'ltr';
}
// Update original title
document.title = TRANSLATIONS[lang].title;
}
function getTrans(key) {
return (TRANSLATIONS[currentLang] && TRANSLATIONS[currentLang][key]) || TRANSLATIONS['en'][key] || key;
}
function initLanguage() {
const savedLang = localStorage.getItem('dekupai_lang') || 'en';
if (elements.langSelect) {
elements.langSelect.value = savedLang;
elements.langSelect.addEventListener('change', (e) => {
changeLanguage(e.target.value);
});
}
changeLanguage(savedLang);
}
// --- CONTROL TOWER CONTROLLER ---
function setupControlTowerListeners() {
// Remove Background click