ميزه: إضافة ميزات جديدة لتحرير PDF، OCR، وإزالة الخلفية مع تفعيل خيارات في ملف البيئة

This commit is contained in:
Your Name
2026-03-08 22:51:50 +02:00
parent d7f6228d7f
commit 0a0c069a58
16 changed files with 242 additions and 62 deletions

View File

@@ -2,6 +2,7 @@ import { lazy, Suspense, useEffect } from 'react';
import { Routes, Route, useLocation } from 'react-router-dom';
import Header from '@/components/layout/Header';
import Footer from '@/components/layout/Footer';
import ErrorBoundary from '@/components/shared/ErrorBoundary';
import { useDirection } from '@/hooks/useDirection';
import { initAnalytics, trackPageView } from '@/services/analytics';
import { useAuthStore } from '@/stores/authStore';
@@ -77,6 +78,7 @@ export default function App() {
<Header />
<main className="container mx-auto flex-1 px-4 py-8 sm:px-6 lg:px-8">
<ErrorBoundary>
<Suspense fallback={<LoadingFallback />}>
<Routes>
{/* Pages */}
@@ -140,6 +142,7 @@ export default function App() {
<Route path="*" element={<NotFoundPage />} />
</Routes>
</Suspense>
</ErrorBoundary>
</main>
<Footer />

View File

@@ -0,0 +1,48 @@
import { Component, type ReactNode } from 'react';
import { AlertTriangle } from 'lucide-react';
interface Props {
children: ReactNode;
fallbackMessage?: string;
}
interface State {
hasError: boolean;
}
export default class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(): State {
return { hasError: true };
}
handleReset = () => {
this.setState({ hasError: false });
};
render() {
if (this.state.hasError) {
return (
<div className="mx-auto max-w-lg py-16 text-center">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-red-100 dark:bg-red-900/30">
<AlertTriangle className="h-8 w-8 text-red-600 dark:text-red-400" />
</div>
<h2 className="mb-2 text-xl font-semibold text-slate-800 dark:text-slate-200">
{this.props.fallbackMessage || 'Something went wrong'}
</h2>
<p className="mb-6 text-sm text-slate-500 dark:text-slate-400">
An unexpected error occurred. Please try again.
</p>
<button
onClick={this.handleReset}
className="rounded-lg bg-primary-600 px-6 py-2 text-sm font-medium text-white hover:bg-primary-700 transition-colors"
>
Try Again
</button>
</div>
);
}
return this.props.children;
}
}

View File

@@ -43,7 +43,7 @@ export default function QrCodeGenerator() {
setError(null);
};
const downloadUrl = result?.download_url ? `/api${result.download_url}` : null;
const downloadUrl = result?.download_url || null;
const schema = generateToolSchema({
name: t('tools.qrCode.title'),

View File

@@ -143,6 +143,17 @@ export default function RemoveBackground() {
</div>
)}
{phase === 'done' && !result && taskError && (
<div className="space-y-4">
<div className="rounded-xl bg-red-50 p-4 text-red-600 dark:bg-red-900/20 dark:text-red-400">
{taskError}
</div>
<button onClick={handleReset} className="btn-secondary w-full">
{t('common.tryAgain')}
</button>
</div>
)}
<AdSlot slot="bottom-banner" format="horizontal" className="mt-6" />
</div>
</>

View File

@@ -23,7 +23,18 @@
"email": "البريد الإلكتروني",
"password": "كلمة المرور",
"darkMode": "الوضع الداكن",
"lightMode": "الوضع الفاتح"
"lightMode": "الوضع الفاتح",
"errors": {
"fileTooLarge": "حجم الملف كبير جدًا. الحد الأقصى المسموح {{size}} ميجابايت.",
"invalidFileType": "نوع الملف غير صالح. الأنواع المقبولة: {{types}}",
"uploadFailed": "فشل رفع الملف. يرجى المحاولة مرة أخرى.",
"processingFailed": "فشلت المعالجة. يرجى المحاولة مرة أخرى.",
"quotaExceeded": "تم استنفاد حصة الاستخدام الشهرية. يرجى المحاولة الشهر القادم.",
"rateLimited": "طلبات كثيرة جدًا. يرجى الانتظار لحظة والمحاولة مجددًا.",
"serverError": "حدث خطأ في الخادم. يرجى المحاولة لاحقًا.",
"networkError": "خطأ في الشبكة. يرجى التحقق من اتصالك والمحاولة مرة أخرى.",
"noFileSelected": "لم يتم اختيار ملف. يرجى اختيار ملف للرفع."
}
},
"auth": {
"forgotPassword": {
@@ -167,7 +178,9 @@
"selectFiles": "اختر ملفات PDF",
"addMore": "أضف ملفات أخرى",
"filesSelected": "{{count}} ملفات مختارة",
"dragToReorder": "اسحب الملفات لإعادة ترتيبها"
"dragToReorder": "اسحب الملفات لإعادة ترتيبها",
"invalidFiles": "يرجى اختيار ملفات PDF صالحة.",
"minFiles": "يرجى اختيار ملفَين على الأقل لدمجهما."
},
"splitPdf": {
"title": "تقسيم PDF",
@@ -205,7 +218,13 @@
"dpiLow": "72 — شاشة",
"dpiMedium": "150 — قياسي",
"dpiHigh": "200 — جيد",
"dpiUltra": "300 — جودة طباعة"
"dpiUltra": "300 — جودة طباعة",
"outputFormat": "صيغة الإخراج",
"quality": "الجودة",
"lowQuality": "شاشة",
"mediumQuality": "قياسي",
"highQuality": "جيد",
"bestQuality": "جودة طباعة"
},
"imagesToPdf": {
"title": "صور إلى PDF",
@@ -213,7 +232,9 @@
"shortDesc": "صور → PDF",
"selectImages": "اختر الصور",
"addMore": "أضف صور أخرى",
"imagesSelected": "{{count}} صور مختارة"
"imagesSelected": "{{count}} صور مختارة",
"invalidFiles": "يرجى اختيار ملفات صور صالحة (JPG أو PNG أو WebP).",
"minFiles": "يرجى اختيار صورة واحدة على الأقل."
},
"watermarkPdf": {
"title": "علامة مائية PDF",
@@ -393,7 +414,12 @@
"pdfToExcel": {
"title": "PDF إلى Excel",
"description": "استخرج الجداول من ملفات PDF وحوّلها إلى جداول بيانات Excel.",
"shortDesc": "PDF → Excel"
"shortDesc": "PDF → Excel",
"errors": {
"noTables": "لم يتم العثور على جداول في هذا الملف. يرجى استخدام ملف PDF يحتوي على بيانات جدولية.",
"processingFailed": "فشل التحويل إلى Excel. يرجى تجربة ملف PDF مختلف.",
"invalidFile": "ملف PDF غير صالح أو تالف. يرجى رفع ملف PDF صحيح."
}
},
"removeWatermark": {
"title": "إزالة العلامة المائية",
@@ -460,7 +486,12 @@
"shortDesc": "استخراج الجداول",
"tablesFound": "تم العثور على {{count}} جدول(جداول)",
"tablePage": "الصفحة {{page}} — الجدول {{index}}",
"noTables": "لم يتم العثور على جداول في هذا المستند."
"noTables": "لم يتم العثور على جداول في هذا المستند.",
"errors": {
"noTables": "لم يتم العثور على جداول في هذا الملف. تعمل الأداة بشكل أفضل مع ملفات PDF التي تحتوي على بيانات جدولية.",
"processingFailed": "فشل استخراج الجداول. يرجى تجربة ملف PDF مختلف.",
"invalidFile": "ملف PDF غير صالح أو تالف. يرجى رفع ملف PDF صحيح."
}
}
},
"account": {

View File

@@ -23,7 +23,18 @@
"email": "Email",
"password": "Password",
"darkMode": "Dark Mode",
"lightMode": "Light Mode"
"lightMode": "Light Mode",
"errors": {
"fileTooLarge": "File is too large. Maximum size is {{size}}MB.",
"invalidFileType": "Invalid file type. Accepted: {{types}}",
"uploadFailed": "Upload failed. Please try again.",
"processingFailed": "Processing failed. Please try again.",
"quotaExceeded": "Monthly usage limit reached. Please try again next month.",
"rateLimited": "Too many requests. Please wait a moment and try again.",
"serverError": "A server error occurred. Please try again later.",
"networkError": "Network error. Please check your connection and try again.",
"noFileSelected": "No file selected. Please choose a file to upload."
}
},
"auth": {
"forgotPassword": {
@@ -167,7 +178,9 @@
"selectFiles": "Select PDF Files",
"addMore": "Add More Files",
"filesSelected": "{{count}} files selected",
"dragToReorder": "Drag files to reorder them"
"dragToReorder": "Drag files to reorder them",
"invalidFiles": "Please select valid PDF files.",
"minFiles": "Please select at least 2 PDF files to merge."
},
"splitPdf": {
"title": "Split PDF",
@@ -205,7 +218,13 @@
"dpiLow": "72 — Screen",
"dpiMedium": "150 — Standard",
"dpiHigh": "200 — Good",
"dpiUltra": "300 — Print Quality"
"dpiUltra": "300 — Print Quality",
"outputFormat": "Output Format",
"quality": "Quality",
"lowQuality": "Screen",
"mediumQuality": "Standard",
"highQuality": "Good",
"bestQuality": "Print Quality"
},
"imagesToPdf": {
"title": "Images to PDF",
@@ -213,7 +232,9 @@
"shortDesc": "Images → PDF",
"selectImages": "Select Images",
"addMore": "Add More Images",
"imagesSelected": "{{count}} images selected"
"imagesSelected": "{{count}} images selected",
"invalidFiles": "Please select valid image files (JPG, PNG, WebP).",
"minFiles": "Please select at least one image."
},
"watermarkPdf": {
"title": "Watermark PDF",
@@ -393,7 +414,12 @@
"pdfToExcel": {
"title": "PDF to Excel",
"description": "Extract tables from PDF files and convert them to Excel spreadsheets.",
"shortDesc": "PDF → Excel"
"shortDesc": "PDF → Excel",
"errors": {
"noTables": "No tables found in this PDF. Please use a PDF that contains tabular data.",
"processingFailed": "Failed to convert to Excel. Please try a different PDF.",
"invalidFile": "Invalid or corrupted PDF file. Please upload a valid PDF."
}
},
"removeWatermark": {
"title": "Remove Watermark",
@@ -460,7 +486,12 @@
"shortDesc": "Extract Tables",
"tablesFound": "{{count}} table(s) found",
"tablePage": "Page {{page}} — Table {{index}}",
"noTables": "No tables were found in this document."
"noTables": "No tables were found in this document.",
"errors": {
"noTables": "No tables found in this PDF. This tool works best with PDFs containing tabular data.",
"processingFailed": "Failed to extract tables. Please try a different PDF.",
"invalidFile": "Invalid or corrupted PDF file. Please upload a valid PDF."
}
}
},
"account": {

View File

@@ -23,7 +23,18 @@
"email": "E-mail",
"password": "Mot de passe",
"darkMode": "Mode sombre",
"lightMode": "Mode clair"
"lightMode": "Mode clair",
"errors": {
"fileTooLarge": "Fichier trop volumineux. Taille maximale autorisée : {{size}} Mo.",
"invalidFileType": "Type de fichier non valide. Formats acceptés : {{types}}",
"uploadFailed": "Téléchargement échoué. Veuillez réessayer.",
"processingFailed": "Échec du traitement. Veuillez réessayer.",
"quotaExceeded": "Limite d'utilisation mensuelle atteinte. Veuillez réessayer le mois prochain.",
"rateLimited": "Trop de requêtes. Veuillez attendre un moment et réessayer.",
"serverError": "Une erreur serveur s'est produite. Veuillez réessayer plus tard.",
"networkError": "Erreur réseau. Veuillez vérifier votre connexion et réessayer.",
"noFileSelected": "Aucun fichier sélectionné. Veuillez choisir un fichier à télécharger."
}
},
"auth": {
"forgotPassword": {
@@ -167,7 +178,9 @@
"selectFiles": "Sélectionner des fichiers PDF",
"addMore": "Ajouter plus de fichiers",
"filesSelected": "{{count}} fichiers sélectionnés",
"dragToReorder": "Glissez les fichiers pour les réorganiser"
"dragToReorder": "Glissez les fichiers pour les réorganiser",
"invalidFiles": "Veuillez sélectionner des fichiers PDF valides.",
"minFiles": "Veuillez sélectionner au moins 2 fichiers PDF à fusionner."
},
"splitPdf": {
"title": "Diviser PDF",
@@ -205,7 +218,13 @@
"dpiLow": "72 — Écran",
"dpiMedium": "150 — Standard",
"dpiHigh": "200 — Bon",
"dpiUltra": "300 — Qualité d'impression"
"dpiUltra": "300 — Qualité d'impression",
"outputFormat": "Format de sortie",
"quality": "Qualité",
"lowQuality": "Écran",
"mediumQuality": "Standard",
"highQuality": "Bon",
"bestQuality": "Qualité impression"
},
"imagesToPdf": {
"title": "Images en PDF",
@@ -213,7 +232,9 @@
"shortDesc": "Images → PDF",
"selectImages": "Sélectionner des images",
"addMore": "Ajouter plus d'images",
"imagesSelected": "{{count}} images sélectionnées"
"imagesSelected": "{{count}} images sélectionnées",
"invalidFiles": "Veuillez sélectionner des fichiers images valides (JPG, PNG, WebP).",
"minFiles": "Veuillez sélectionner au moins une image."
},
"watermarkPdf": {
"title": "Filigrane PDF",
@@ -393,7 +414,12 @@
"pdfToExcel": {
"title": "PDF vers Excel",
"description": "Extrayez les tableaux des fichiers PDF et convertissez-les en feuilles de calcul Excel.",
"shortDesc": "PDF → Excel"
"shortDesc": "PDF → Excel",
"errors": {
"noTables": "Aucun tableau trouvé dans ce PDF. Veuillez utiliser un PDF contenant des données tabulaires.",
"processingFailed": "Échec de la conversion en Excel. Veuillez essayer un autre PDF.",
"invalidFile": "Fichier PDF invalide ou corrompu. Veuillez télécharger un PDF valide."
}
},
"removeWatermark": {
"title": "Supprimer le filigrane",
@@ -460,7 +486,12 @@
"shortDesc": "Extraire les tableaux",
"tablesFound": "{{count}} tableau(x) trouvé(s)",
"tablePage": "Page {{page}} — Tableau {{index}}",
"noTables": "Aucun tableau n'a été trouvé dans ce document."
"noTables": "Aucun tableau n'a été trouvé dans ce document.",
"errors": {
"noTables": "Aucun tableau trouvé dans ce PDF. Cet outil fonctionne mieux avec des PDF contenant des données tabulaires.",
"processingFailed": "Échec de l'extraction des tableaux. Veuillez essayer un autre PDF.",
"invalidFile": "Fichier PDF invalide ou corrompu. Veuillez télécharger un PDF valide."
}
}
},
"account": {