diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 8032f1d50..8a0c92367 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -10,7 +10,8 @@ "Bash(npm test)", "Bash(npm test:*)", "Bash(ls:*)", - "Bash(npx tsc:*)" + "Bash(npx tsc:*)", + "Bash(sed:*)" ], "deny": [] } diff --git a/frontend/index.html b/frontend/index.html index c4a808349..31f1b3008 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,5 +1,5 @@ - + diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index 6bc29b577..0f33b98e4 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -383,14 +383,6 @@ "title": "Remove", "desc": "Delete unwanted pages from your PDF document." }, - "addPassword": { - "title": "Add Password", - "desc": "Encrypt your PDF document with a password." - }, - "changePermissions": { - "title": "Change Permissions", - "desc": "Change document restrictions and permissions." - }, "removePassword": { "title": "Remove Password", "desc": "Remove password protection from your PDF document." @@ -399,10 +391,6 @@ "title": "Compress", "desc": "Compress PDFs to reduce their file size." }, - "sanitize": { - "title": "Sanitise", - "desc": "Remove potentially harmful elements from PDF files." - }, "unlockPDFForms": { "title": "Unlock PDF Forms", "desc": "Remove read-only property of form fields in a PDF document." @@ -1751,6 +1739,8 @@ "approximateSize": "Approximate size" }, "sanitize": { + "title": "Sanitise", + "desc": "Remove potentially harmful elements from PDF files.", "submit": "Sanitise PDF", "completed": "Sanitisation completed successfully", "error.generic": "Sanitisation failed", @@ -1783,6 +1773,8 @@ } }, "addPassword": { + "title": "Add Password", + "desc": "Encrypt your PDF document with a password.", "completed": "Password protection applied", "submit": "Encrypt", "filenamePrefix": "encrypted", @@ -1835,6 +1827,8 @@ } }, "changePermissions": { + "title": "Change Permissions", + "desc": "Change document restrictions and permissions.", "completed": "Permissions changed", "submit": "Change Permissions", "title": "Document Permissions", diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json deleted file mode 100644 index 8dc3e4f90..000000000 --- a/frontend/public/locales/en/translation.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "convert": { - "selectSourceFormat": "Select source file format", - "selectTargetFormat": "Select target file format", - "selectFirst": "Select a source format first", - "imageOptions": "Image Options:", - "emailOptions": "Email Options:", - "colorType": "Color Type", - "dpi": "DPI", - "singleOrMultiple": "Output", - "emailNote": "Email attachments and embedded images will be included" - }, - "common": { - "color": "Color", - "grayscale": "Grayscale", - "blackWhite": "Black & White", - "single": "Single Image", - "multiple": "Multiple Images" - }, - "groups": { - "document": "Document", - "spreadsheet": "Spreadsheet", - "presentation": "Presentation", - "image": "Image", - "web": "Web", - "text": "Text", - "email": "Email" - } -} \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 852204b25..d2aec8242 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Suspense } from 'react'; import { RainbowThemeProvider } from './components/shared/RainbowThemeProvider'; import { FileContextProvider } from './contexts/FileContext'; import { FilesModalProvider } from './contexts/FilesModalContext'; @@ -8,14 +8,30 @@ import HomePage from './pages/HomePage'; import './styles/tailwind.css'; import './index.css'; +// Loading component for i18next suspense +const LoadingFallback = () => ( +
+ Loading... +
+); + export default function App() { return ( - - - - - - - + }> + + + + + + + + ); } diff --git a/frontend/src/components/FileManager.tsx b/frontend/src/components/FileManager.tsx index 02f9af5e4..5f6af568b 100644 --- a/frontend/src/components/FileManager.tsx +++ b/frontend/src/components/FileManager.tsx @@ -108,7 +108,7 @@ const FileManager: React.FC = ({ selectedTool }) => { className="overflow-hidden p-0" withCloseButton={false} styles={{ - content: { + content: { position: 'relative', margin: isMobile ? '1rem' : '2rem' }, @@ -116,12 +116,12 @@ const FileManager: React.FC = ({ selectedTool }) => { header: { display: 'none' } }} > -
= ({ selectedTool }) => { onDrop={handleNewFileUpload} onDragEnter={() => setIsDragging(true)} onDragLeave={() => setIsDragging(false)} - accept={["*/*"]} + accept={["*/*"] as any} multiple={true} activateOnClick={false} - style={{ - height: '100%', + style={{ + height: '100%', width: '100%', border: 'none', borderRadius: '30px', @@ -158,11 +158,11 @@ const FileManager: React.FC = ({ selectedTool }) => { {isMobile ? : } - +
); }; -export default FileManager; \ No newline at end of file +export default FileManager; diff --git a/frontend/src/components/fileEditor/FileEditor.tsx b/frontend/src/components/fileEditor/FileEditor.tsx index 1494dfa9a..c45e7e902 100644 --- a/frontend/src/components/fileEditor/FileEditor.tsx +++ b/frontend/src/components/fileEditor/FileEditor.tsx @@ -664,7 +664,6 @@ const FileEditor = ({ return ( { ref={fileInputRef} type="file" multiple={true} - accept="*/*" + accept={["*/*"] as any} onChange={onFileInputChange} style={{ display: 'none' }} data-testid="file-input" @@ -17,4 +17,4 @@ const HiddenFileInput: React.FC = () => { ); }; -export default HiddenFileInput; \ No newline at end of file +export default HiddenFileInput; diff --git a/frontend/src/components/shared/LandingPage.tsx b/frontend/src/components/shared/LandingPage.tsx index 40b765547..52d410cdb 100644 --- a/frontend/src/components/shared/LandingPage.tsx +++ b/frontend/src/components/shared/LandingPage.tsx @@ -33,7 +33,7 @@ const LandingPage = () => { {/* White PDF Page Background */} { > - {t('fileUpload.addFiles', 'Add Files')} + {t('fileUpload.uploadFiles', 'Upload Files')} @@ -125,7 +125,7 @@ const LandingPage = () => { ref={fileInputRef} type="file" multiple - accept=".pdf,.zip" + accept="*/*" onChange={handleFileSelect} style={{ display: 'none' }} /> @@ -137,7 +137,7 @@ const LandingPage = () => { className="text-[var(--accent-interactive)]" style={{ fontSize: '.8rem' }} > - {t('fileUpload.dragFilesInOrClick', 'Drag files in or click "Add Files" to browse')} + {t('fileUpload.dropFilesHere', 'Drop files here or click to upload')} @@ -145,4 +145,4 @@ const LandingPage = () => { ); }; -export default LandingPage; \ No newline at end of file +export default LandingPage; diff --git a/frontend/src/hooks/useToolManagement.tsx b/frontend/src/hooks/useToolManagement.tsx index aac9c5421..35a93765f 100644 --- a/frontend/src/hooks/useToolManagement.tsx +++ b/frontend/src/hooks/useToolManagement.tsx @@ -142,9 +142,9 @@ export const useToolManagement = (): ToolManagementResult => { const toolDef = toolDefinitions[toolKey]; availableTools[toolKey] = { ...toolDef, - name: t(`home.${toolKey}.title`, toolKey.charAt(0).toUpperCase() + toolKey.slice(1)), - title: t(`home.${toolKey}.title`, toolDef.description || toolKey), - description: t(`home.${toolKey}.desc`, toolDef.description || `${toolKey} tool`) + name: t(`${toolKey}.title`, toolKey.charAt(0).toUpperCase() + toolKey.slice(1)), + title: t(`${toolKey}.title`, toolDef.description || toolKey), + description: t(`${toolKey}.desc`, toolDef.description || `${toolKey} tool`) }; } }); diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index a33a29929..454bb4cbc 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -5,6 +5,7 @@ import Backend from 'i18next-http-backend'; // Define supported languages (based on your existing translations) export const supportedLanguages = { + 'en': 'English', 'en-GB': 'English (UK)', 'en-US': 'English (US)', 'ar-AR': 'العربية', @@ -57,26 +58,42 @@ i18n .use(initReactI18next) .init({ fallbackLng: 'en-GB', + supportedLngs: Object.keys(supportedLanguages), + nonExplicitSupportedLngs: false, debug: process.env.NODE_ENV === 'development', - + + // Ensure synchronous loading to prevent timing issues + initImmediate: false, + interpolation: { escapeValue: false, // React already escapes values }, - + backend: { - loadPath: '/locales/{{lng}}/{{ns}}.json', + loadPath: (lngs: string[], namespaces: string[]) => { + // Map 'en' to 'en-GB' for loading translations + const lng = lngs[0] === 'en' ? 'en-GB' : lngs[0]; + return `/locales/${lng}/${namespaces[0]}.json`; + }, }, - + detection: { order: ['localStorage', 'navigator', 'htmlTag'], caches: ['localStorage'], + convertDetectedLanguage: (lng: string) => lng === 'en' ? 'en-GB' : lng, }, - + react: { - useSuspense: false, // Set to false to avoid suspense issues with SSR + useSuspense: true, // Enable suspense to prevent premature rendering + bindI18n: 'languageChanged loaded', + bindI18nStore: 'added removed', + transEmptyNodeValue: '', // Return empty string for missing keys instead of key name + transSupportBasicHtmlNodes: true, + transKeepBasicHtmlNodesFor: ['br', 'strong', 'i', 'p'], }, }); + // Set document direction based on language i18n.on('languageChanged', (lng) => { const isRTL = rtlLanguages.includes(lng); @@ -84,4 +101,4 @@ i18n.on('languageChanged', (lng) => { document.documentElement.lang = lng; }); -export default i18n; \ No newline at end of file +export default i18n;