feat(i18n): update translations and improve error handling messages

- Updated site tagline and footer description in multiple languages.
- Enhanced error messages for various scenarios in the API service.
- Added translations for new error codes related to AI features and PDF processing.
- Improved user feedback in the UI components by utilizing i18n for dynamic text.
- Refactored error handling in the API service to map backend error codes to user-friendly messages.
This commit is contained in:
Your Name
2026-04-05 10:12:22 +02:00
parent 8693834230
commit ade7abac46
14 changed files with 607 additions and 88 deletions

View File

@@ -2,49 +2,64 @@ import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { ArrowRight, FileText, Layers3 } from 'lucide-react';
const FOOTER_TOOLS = {
interface FooterTool {
slug: string;
i18nKey: string;
isLanding?: boolean;
isComparison?: boolean;
}
const FOOTER_TOOLS: Record<string, FooterTool[]> = {
PDF: [
{ slug: 'pdf-to-word', label: 'PDF to Word' },
{ slug: 'compress-pdf', label: 'Compress PDF' },
{ slug: 'merge-pdf', label: 'Merge PDF' },
{ slug: 'split-pdf', label: 'Split PDF' },
{ slug: 'pdf-to-images', label: 'PDF to Images' },
{ slug: 'protect-pdf', label: 'Protect PDF' },
{ slug: 'watermark-pdf', label: 'Watermark PDF' },
{ slug: 'pdf-editor', label: 'PDF Editor' },
{ slug: 'pdf-to-word', i18nKey: 'tools.pdfToWord.title' },
{ slug: 'compress-pdf', i18nKey: 'tools.compressPdf.title' },
{ slug: 'merge-pdf', i18nKey: 'tools.mergePdf.title' },
{ slug: 'split-pdf', i18nKey: 'tools.splitPdf.title' },
{ slug: 'pdf-to-images', i18nKey: 'tools.pdfToImages.title' },
{ slug: 'protect-pdf', i18nKey: 'tools.protectPdf.title' },
{ slug: 'watermark-pdf', i18nKey: 'tools.watermarkPdf.title' },
{ slug: 'pdf-editor', i18nKey: 'tools.pdfEditor.title' },
],
'Image & Convert': [
{ slug: 'compress-image', label: 'Compress Image' },
{ slug: 'image-converter', label: 'Image Converter' },
{ slug: 'image-resize', label: 'Image Resize' },
{ slug: 'remove-background', label: 'Remove Background' },
{ slug: 'word-to-pdf', label: 'Word to PDF' },
{ slug: 'html-to-pdf', label: 'HTML to PDF' },
{ slug: 'pdf-to-excel', label: 'PDF to Excel' },
{ slug: 'compress-image', i18nKey: 'tools.compressImage.title' },
{ slug: 'image-converter', i18nKey: 'tools.imageConvert.title' },
{ slug: 'image-resize', i18nKey: 'tools.imageResize.title' },
{ slug: 'remove-background', i18nKey: 'tools.removeBg.title' },
{ slug: 'word-to-pdf', i18nKey: 'tools.wordToPdf.title' },
{ slug: 'html-to-pdf', i18nKey: 'tools.htmlToPdf.title' },
{ slug: 'pdf-to-excel', i18nKey: 'tools.pdfToExcel.title' },
],
'AI & Utility': [
{ slug: 'chat-pdf', label: 'Chat with PDF' },
{ slug: 'summarize-pdf', label: 'Summarize PDF' },
{ slug: 'translate-pdf', label: 'Translate PDF' },
{ slug: 'ocr', label: 'OCR' },
{ slug: 'qr-code', label: 'QR Code Generator' },
{ slug: 'video-to-gif', label: 'Video to GIF' },
{ slug: 'word-counter', label: 'Word Counter' },
{ slug: 'chat-pdf', i18nKey: 'tools.chatPdf.title' },
{ slug: 'summarize-pdf', i18nKey: 'tools.summarizePdf.title' },
{ slug: 'translate-pdf', i18nKey: 'tools.translatePdf.title' },
{ slug: 'ocr', i18nKey: 'tools.ocr.title' },
{ slug: 'qr-code', i18nKey: 'tools.qrCode.title' },
{ slug: 'video-to-gif', i18nKey: 'tools.videoToGif.title' },
{ slug: 'word-counter', i18nKey: 'tools.wordCounter.title' },
],
Guides: [
{ slug: 'best-pdf-tools', label: 'Best PDF Tools', isLanding: true },
{ slug: 'free-pdf-tools-online', label: 'Free PDF Tools Online', isLanding: true },
{ slug: 'convert-files-online', label: 'Convert Files Online', isLanding: true },
{ slug: 'best-pdf-tools', i18nKey: 'footer.guides.bestPdfTools', isLanding: true },
{ slug: 'free-pdf-tools-online', i18nKey: 'footer.guides.freePdfToolsOnline', isLanding: true },
{ slug: 'convert-files-online', i18nKey: 'footer.guides.convertFilesOnline', isLanding: true },
],
Comparisons: [
{ slug: 'compress-pdf-vs-ilovepdf', label: 'Dociva vs iLovePDF', isComparison: true },
{ slug: 'merge-pdf-vs-smallpdf', label: 'Dociva vs Smallpdf', isComparison: true },
{ slug: 'pdf-to-word-vs-adobe-acrobat', label: 'Dociva vs Adobe Acrobat', isComparison: true },
{ slug: 'compress-image-vs-tinypng', label: 'Dociva vs TinyPNG', isComparison: true },
{ slug: 'ocr-vs-adobe-scan', label: 'Dociva vs Adobe Scan', isComparison: true },
{ slug: 'compress-pdf-vs-ilovepdf', i18nKey: 'footer.comparisons.compressPdfVsIlovepdf', isComparison: true },
{ slug: 'merge-pdf-vs-smallpdf', i18nKey: 'footer.comparisons.mergePdfVsSmallpdf', isComparison: true },
{ slug: 'pdf-to-word-vs-adobe-acrobat', i18nKey: 'footer.comparisons.pdfToWordVsAdobeAcrobat', isComparison: true },
{ slug: 'compress-image-vs-tinypng', i18nKey: 'footer.comparisons.compressImageVsTinypng', isComparison: true },
{ slug: 'ocr-vs-adobe-scan', i18nKey: 'footer.comparisons.ocrVsAdobeScan', isComparison: true },
],
};
const CATEGORY_KEYS: Record<string, string> = {
'PDF': 'footer.categories.pdf',
'Image & Convert': 'footer.categories.imageConvert',
'AI & Utility': 'footer.categories.aiUtility',
'Guides': 'footer.categories.guides',
'Comparisons': 'footer.categories.comparisons',
};
export default function Footer() {
const { t } = useTranslation();
@@ -63,16 +78,13 @@ export default function Footer() {
{t('common.appName')}
</p>
<p className="text-sm text-slate-500 dark:text-slate-400">
{t('common.siteTagline', 'Online PDF and file workflows')}
{t('common.siteTagline')}
</p>
</div>
</div>
<p className="mt-6 max-w-md text-sm leading-7 text-slate-600 dark:text-slate-300">
{t(
'common.footerDescription',
'Convert, compress, edit, and automate document work in one browser-based workspace built for speed, clarity, and secure processing.'
)}
{t('common.footerDescription')}
</p>
<div className="mt-6 flex flex-wrap gap-3">
@@ -96,16 +108,16 @@ export default function Footer() {
{Object.entries(FOOTER_TOOLS).map(([category, tools]) => (
<div key={category}>
<h3 className="mb-4 text-xs font-bold uppercase tracking-[0.22em] text-slate-500 dark:text-slate-400">
{category}
{t(CATEGORY_KEYS[category] ?? category)}
</h3>
<ul className="space-y-2.5">
{tools.map((tool) => (
<li key={tool.slug}>
<Link
to={(tool as { slug: string; isLanding?: boolean; isComparison?: boolean }).isComparison ? `/compare/${tool.slug}` : (tool as { slug: string; isLanding?: boolean }).isLanding ? `/${tool.slug}` : `/tools/${tool.slug}`}
to={tool.isComparison ? `/compare/${tool.slug}` : tool.isLanding ? `/${tool.slug}` : `/tools/${tool.slug}`}
className="text-sm text-slate-600 transition-colors hover:text-primary-600 dark:text-slate-300 dark:hover:text-primary-400"
>
{tool.label}
{t(tool.i18nKey)}
</Link>
</li>
))}

View File

@@ -110,7 +110,7 @@ export default function Header() {
{t('common.appName')}
</span>
<span className="hidden text-xs font-medium text-slate-500 dark:text-slate-400 sm:block">
{t('common.siteTagline', 'Online PDF and file workflows')}
{t('common.siteTagline')}
</span>
</div>
</Link>
@@ -145,7 +145,7 @@ export default function Header() {
className="hidden items-center gap-2 rounded-full bg-slate-950 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition-all hover:-translate-y-0.5 hover:bg-primary-600 lg:inline-flex dark:bg-white dark:text-slate-950 dark:hover:bg-primary-300"
>
<Sparkles className="h-4 w-4" />
{t('home.startFree', 'Start Free')}
{t('home.startFree')}
<ArrowRight className="h-3.5 w-3.5" />
</Link>
) : null}