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;