From b7c70a9ccf4e30df8d8c95019d956d6dd8628abc Mon Sep 17 00:00:00 2001 From: tresor228 Date: Tue, 14 Oct 2025 19:40:55 +0000 Subject: [PATCH] language --- package-lock.json | 180 ++++++++++++++++++++- package.json | 3 + public/locales/en/common.json | 132 +++++++++++++++ public/locales/fr/common.json | 132 +++++++++++++++ src/app/api/order.ts | 2 +- src/app/layout.tsx | 11 +- src/app/page.tsx | 51 ++++-- src/app/sign/page.tsx | 4 +- src/components/LanguageSwitcher.tsx | 76 +++++++++ src/components/LanguageSwitcherCompact.tsx | 74 +++++++++ src/components/LanguageSwitcherMinimal.tsx | 72 +++++++++ src/components/button2.tsx | 4 +- src/components/nav.tsx | 25 ++- src/components/navbar1.tsx | 1 - src/components/ui/resizable-navbar.tsx | 2 +- src/contexts/LanguageContext.tsx | 46 ++++++ src/hooks/useTranslations.ts | 70 ++++++++ src/lib/supabaseclient.ts | 2 +- 18 files changed, 854 insertions(+), 33 deletions(-) create mode 100644 public/locales/en/common.json create mode 100644 public/locales/fr/common.json create mode 100644 src/components/LanguageSwitcher.tsx create mode 100644 src/components/LanguageSwitcherCompact.tsx create mode 100644 src/components/LanguageSwitcherMinimal.tsx create mode 100644 src/contexts/LanguageContext.tsx create mode 100644 src/hooks/useTranslations.ts diff --git a/package-lock.json b/package-lock.json index 735c970..43d1514 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,16 +17,19 @@ "clsx": "^2.1.1", "framer-motion": "^12.19.1", "gsap": "^3.13.0", + "i18next": "^25.6.0", "lucide-react": "^0.518.0", "mini-svg-data-uri": "^1.4.4", "motion": "^12.20.1", "next": "^15.5.4", + "next-i18next": "^15.4.2", "ogl": "^1.0.11", "radix-ui": "^1.4.2", "razorpay": "^2.9.6", "react": "^19.0.0", "react-dom": "^19.0.0", "react-fast-marquee": "^1.6.5", + "react-i18next": "^16.0.1", "react-use-measure": "^2.1.7", "tailwind-merge": "^3.3.1" }, @@ -72,6 +75,15 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@emnapi/core": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", @@ -2995,6 +3007,18 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz", + "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==", + "license": "MIT", + "dependencies": { + "hoist-non-react-statics": "^3.3.0" + }, + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3028,8 +3052,8 @@ "version": "19.1.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", - "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -3040,6 +3064,7 @@ "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.0.0" } @@ -3099,6 +3124,7 @@ "integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.34.1", "@typescript-eslint/types": "8.34.1", @@ -3615,6 +3641,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4013,6 +4040,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001718", "electron-to-chromium": "^1.5.160", @@ -4198,6 +4226,17 @@ "dev": true, "license": "MIT" }, + "node_modules/core-js": { + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.46.0.tgz", + "integrity": "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4217,7 +4256,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -4624,6 +4662,7 @@ "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -4798,6 +4837,7 @@ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -5545,6 +5585,62 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/i18next": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.6.0.tgz", + "integrity": "sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.27.6" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-fs-backend": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.6.0.tgz", + "integrity": "sha512-3ZlhNoF9yxnM8pa8bWp5120/Ob6t4lVl1l/tbLmkml/ei3ud8IWySCHt2lrY5xWRlSU5D9IV2sm5bEbGuTqwTw==", + "license": "MIT" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -6714,6 +6810,42 @@ } } }, + "node_modules/next-i18next": { + "version": "15.4.2", + "resolved": "https://registry.npmjs.org/next-i18next/-/next-i18next-15.4.2.tgz", + "integrity": "sha512-zgRxWf7kdXtM686ecGIBQL+Bq0+DqAhRlasRZ3vVF0TmrNTWkVhs52n//oU3Fj5O7r/xOKkECDUwfOuXVwTK/g==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + }, + { + "type": "individual", + "url": "https://locize.com" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@types/hoist-non-react-statics": "^3.3.6", + "core-js": "^3", + "hoist-non-react-statics": "^3.3.2", + "i18next-fs-backend": "^2.6.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "i18next": ">= 23.7.13", + "next": ">= 12.0.0", + "react": ">= 17.0.2", + "react-i18next": ">= 13.5.0" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -7045,6 +7177,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -7211,6 +7344,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -7220,6 +7354,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -7237,11 +7372,37 @@ "react-dom": ">= 16.8.0 || ^18.0.0" } }, + "node_modules/react-i18next": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.0.1.tgz", + "integrity": "sha512-0S//bpYEkCPjzuVmxDf9Z6+Y+ArNvpAUk7eDL4qNCZXjDh6Z9j6MZ+NThU7kMCOsmYmDCun3GYEwkiOjjZo9Ug==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.27.6", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 25.5.2", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/react-remove-scroll": { @@ -8011,6 +8172,7 @@ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -8174,8 +8336,9 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8337,6 +8500,15 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index 6f9fe11..aa92460 100644 --- a/package.json +++ b/package.json @@ -18,16 +18,19 @@ "clsx": "^2.1.1", "framer-motion": "^12.19.1", "gsap": "^3.13.0", + "i18next": "^25.6.0", "lucide-react": "^0.518.0", "mini-svg-data-uri": "^1.4.4", "motion": "^12.20.1", "next": "^15.5.4", + "next-i18next": "^15.4.2", "ogl": "^1.0.11", "radix-ui": "^1.4.2", "razorpay": "^2.9.6", "react": "^19.0.0", "react-dom": "^19.0.0", "react-fast-marquee": "^1.6.5", + "react-i18next": "^16.0.1", "react-use-measure": "^2.1.7", "tailwind-merge": "^3.3.1" }, diff --git a/public/locales/en/common.json b/public/locales/en/common.json new file mode 100644 index 0000000..a73c3cc --- /dev/null +++ b/public/locales/en/common.json @@ -0,0 +1,132 @@ +{ + "navigation": { + "home": "Home", + "dashboard": "Dashboard", + "signIn": "Sign In", + "signUp": "Sign Up", + "logout": "Logout" + }, + "hero": { + "badge": "Discover Genuine Solutions", + "title": "Want help in", + "subtitle": "Get professional assignment writing services from verified experts. 100% AI-free, plagiarism-free content.", + "categories": { + "projects": "Projects", + "assignments": "assignments", + "termwork": "Termwork", + "ppt": "PPT" + } + }, + "steps": { + "title": "Just 3 steps to get started", + "subtitle": "HOW IT WORKS", + "step1": { + "title": "Upload Your Data", + "description": "Simply upload your data to our secure platform. We support various file formats and data types to ensure a seamless integration with your existing systems." + }, + "step2": { + "title": "Click Start", + "description": "Our advanced AI algorithms automatically process and analyze your data, extracting valuable insights and patterns that would be difficult to identify manually." + }, + "step3": { + "title": "Get Actionable Insights", + "description": "Receive clear, actionable insights and recommendations based on the AI analysis. Use these insights to make data-driven decisions and improve your business strategies." + } + }, + "form": { + "personalInfo": { + "title": "Personal Information", + "step": "Step 1 of 4", + "firstName": "First Name", + "lastName": "Last Name", + "email": "Email", + "phone": "Phone Number", + "firstNamePlaceholder": "e.g., John", + "lastNamePlaceholder": "e.g., Doe", + "emailPlaceholder": "e.g., john.doe@example.com", + "phonePlaceholder": "e.g., +1 (555) 123-4567" + }, + "academicInfo": { + "title": "Academic Information", + "step": "Step 2 of 4", + "subject": "Subject", + "course": "Course", + "branch": "Branch", + "semester": "Semester", + "subjectPlaceholder": "e.g., Mathematics", + "coursePlaceholder": "e.g., Bachelor of Engineering", + "branchPlaceholder": "e.g., Computer Science", + "semesterPlaceholder": "e.g., 3rd" + }, + "assignmentDetails": { + "title": "Assignment Details", + "step": "Step 3 of 4", + "instructions": "Instructions", + "instructionsPlaceholder": "Describe your assignment requirements...", + "uploadFile": "Upload Assignment File", + "uploadNotes": "Upload Additional Notes (Optional)", + "fileTypes": "PDF, DOC, DOCX (Max 10MB)", + "noOfPages": "Number of Pages", + "extraPages": "Extra Pages", + "terms": "I agree to the terms and conditions" + }, + "payment": { + "title": "Payment", + "step": "Step 4 of 4", + "totalAmount": "Total Amount", + "proceedToPayment": "Proceed to Payment" + }, + "buttons": { + "next": "Next", + "previous": "Previous", + "submit": "Submit", + "upload": "Upload File" + }, + "required": "*" + }, + "dashboard": { + "title": "Academic Hub", + "stats": { + "plagiarism": "0% Plagiarism", + "projectsDelivered": "5K+ Projects Delivered", + "successRate": "98% Success Rate", + "support": "24/7 Support" + }, + "categories": { + "assignments": "Assignments", + "projects": "Projects", + "presentations": "Presentations", + "termwork": "Termwork" + }, + "featuredProjects": { + "assignmentMed": "Assignment-Med", + "termworkMed": "Termwork-Med", + "ppts": "PPTs", + "tags": { + "bestSeller": "Best Seller", + "new": "New", + "popular": "Popular" + }, + "levels": { + "advanced": "Advanced", + "expert": "Expert", + "intermediate": "Intermediate" + }, + "duration": { + "days": "days", + "weeks": "weeks" + } + } + }, + "footer": { + "copyright": "Copyright", + "allRightsReserved": "All rights reserved" + }, + "common": { + "loading": "Loading...", + "error": "Error", + "success": "Success", + "required": "This field is required", + "optional": "Optional" + } +} diff --git a/public/locales/fr/common.json b/public/locales/fr/common.json new file mode 100644 index 0000000..6dd8b02 --- /dev/null +++ b/public/locales/fr/common.json @@ -0,0 +1,132 @@ +{ + "navigation": { + "home": "Accueil", + "dashboard": "Tableau de bord", + "signIn": "Se connecter", + "signUp": "S'inscrire", + "logout": "Déconnexion" + }, + "hero": { + "badge": "Découvrez des Solutions Authentiques", + "title": "Besoin d'aide pour", + "subtitle": "Obtenez des services d'écriture d'assignations professionnels auprès d'experts vérifiés. Contenu 100% sans IA, sans plagiat.", + "categories": { + "projects": "Projets", + "assignments": "assignations", + "termwork": "Travaux pratiques", + "ppt": "Présentations" + } + }, + "steps": { + "title": "Seulement 3 étapes pour commencer", + "subtitle": "COMMENT ÇA MARCHE", + "step1": { + "title": "Téléchargez Vos Données", + "description": "Téléchargez simplement vos données sur notre plateforme sécurisée. Nous prenons en charge divers formats de fichiers et types de données pour assurer une intégration transparente avec vos systèmes existants." + }, + "step2": { + "title": "Cliquez sur Démarrer", + "description": "Nos algorithmes IA avancés traitent et analysent automatiquement vos données, extrayant des informations précieuses et des modèles qui seraient difficiles à identifier manuellement." + }, + "step3": { + "title": "Obtenez des Informations Exploitables", + "description": "Recevez des informations claires et exploitables et des recommandations basées sur l'analyse IA. Utilisez ces informations pour prendre des décisions basées sur les données et améliorer vos stratégies commerciales." + } + }, + "form": { + "personalInfo": { + "title": "Informations Personnelles", + "step": "Étape 1 sur 4", + "firstName": "Prénom", + "lastName": "Nom de famille", + "email": "Email", + "phone": "Numéro de téléphone", + "firstNamePlaceholder": "ex., Jean", + "lastNamePlaceholder": "ex., Dupont", + "emailPlaceholder": "ex., jean.dupont@exemple.com", + "phonePlaceholder": "ex., +33 1 23 45 67 89" + }, + "academicInfo": { + "title": "Informations Académiques", + "step": "Étape 2 sur 4", + "subject": "Matière", + "course": "Formation", + "branch": "Spécialité", + "semester": "Semestre", + "subjectPlaceholder": "ex., Mathématiques", + "coursePlaceholder": "ex., Licence d'Ingénierie", + "branchPlaceholder": "ex., Informatique", + "semesterPlaceholder": "ex., 3ème" + }, + "assignmentDetails": { + "title": "Détails de l'Assignation", + "step": "Étape 3 sur 4", + "instructions": "Instructions", + "instructionsPlaceholder": "Décrivez les exigences de votre assignation...", + "uploadFile": "Télécharger le Fichier d'Assignation", + "uploadNotes": "Télécharger des Notes Supplémentaires (Optionnel)", + "fileTypes": "PDF, DOC, DOCX (Max 10MB)", + "noOfPages": "Nombre de Pages", + "extraPages": "Pages Supplémentaires", + "terms": "J'accepte les termes et conditions" + }, + "payment": { + "title": "Paiement", + "step": "Étape 4 sur 4", + "totalAmount": "Montant Total", + "proceedToPayment": "Procéder au Paiement" + }, + "buttons": { + "next": "Suivant", + "previous": "Précédent", + "submit": "Soumettre", + "upload": "Télécharger le Fichier" + }, + "required": "*" + }, + "dashboard": { + "title": "Hub Académique", + "stats": { + "plagiarism": "0% de Plagiat", + "projectsDelivered": "5K+ Projets Livrés", + "successRate": "98% de Taux de Réussite", + "support": "Support 24/7" + }, + "categories": { + "assignments": "Assignations", + "projects": "Projets", + "presentations": "Présentations", + "termwork": "Travaux Pratiques" + }, + "featuredProjects": { + "assignmentMed": "Assignation-Méd", + "termworkMed": "Travaux-Méd", + "ppts": "Présentations", + "tags": { + "bestSeller": "Meilleure Vente", + "new": "Nouveau", + "popular": "Populaire" + }, + "levels": { + "advanced": "Avancé", + "expert": "Expert", + "intermediate": "Intermédiaire" + }, + "duration": { + "days": "jours", + "weeks": "semaines" + } + } + }, + "footer": { + "copyright": "Copyright", + "allRightsReserved": "Tous droits réservés" + }, + "common": { + "loading": "Chargement...", + "error": "Erreur", + "success": "Succès", + "required": "Ce champ est requis", + "optional": "Optionnel" + } +} diff --git a/src/app/api/order.ts b/src/app/api/order.ts index 7b4b134..2beceed 100644 --- a/src/app/api/order.ts +++ b/src/app/api/order.ts @@ -26,4 +26,4 @@ export async function POST(req: NextRequest) { const errorMessage = error instanceof Error ? error.message : 'Order creation failed'; return NextResponse.json({ error: errorMessage }, { status: 500 }); } -} +} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3314e47..2673d1b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,12 +1,13 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; +import { LanguageProvider } from "@/contexts/LanguageContext"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "AsHelp - Academic Assignment Platform", + description: "Connecting students with verified toppers for seamless academic assistance", }; export default function RootLayout({ @@ -16,7 +17,11 @@ export default function RootLayout({ }>) { return ( - {children} + + + {children} + + ); } diff --git a/src/app/page.tsx b/src/app/page.tsx index 7e375a0..7756f1b 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -10,10 +10,12 @@ import { NavbarDemo } from "@/components/nav"; import Image from 'next/image'; import { AnimatePresence } from "framer-motion"; import HomePageSkeleton from "@/components/skeletons/homePageSkeleton"; +import { useTranslations } from '@/hooks/useTranslations'; export default function BackgroundBoxesDemo() { + const { t } = useTranslations(); const [scrollY, setScrollY] = useState(0); const [isLoading, setIsLoading] = useState(true); const [mounted, setMounted] = useState(false); @@ -80,21 +82,26 @@ export default function BackgroundBoxesDemo() { {/* Button Row */} {/* Heading + Flip Row */}

- Want help in + {t('hero.title', 'Want help in')}

{/* Description Row */}
- Get professional assignment writing services from verified experts. 100% AI-free, plagiarism-free content. + {t('hero.subtitle', 'Get professional assignment writing services from verified experts. 100% AI-free, plagiarism-free content.')}
@@ -132,8 +139,12 @@ export default function BackgroundBoxesDemo() {
{/* 3 Steps Section */}
-
HOW IT WORKS
-

Just 3 steps to get started

+
+ {t('steps.subtitle', 'HOW IT WORKS')} +
+

+ {t('steps.title', 'Just 3 steps to get started')} +

{/* Step 1 */}
@@ -141,8 +152,12 @@ export default function BackgroundBoxesDemo() {
-

1. Upload Your Data

-

Simply upload your data to our secure platform. We support various file formats and data types to ensure a seamless integration with your existing systems.

+

+ 1. {t('steps.step1.title', 'Upload Your Data')} +

+

+ {t('steps.step1.description', 'Simply upload your data to our secure platform. We support various file formats and data types to ensure a seamless integration with your existing systems.')} +

{/* Step 2 */} @@ -151,8 +166,12 @@ export default function BackgroundBoxesDemo() {
-

2. Click Start

-

Our advanced AI algorithms automatically process and analyze your data, extracting valuable insights and patterns that would be difficult to identify manually.

+

+ 2. {t('steps.step2.title', 'Click Start')} +

+

+ {t('steps.step2.description', 'Our advanced AI algorithms automatically process and analyze your data, extracting valuable insights and patterns that would be difficult to identify manually.')} +

{/* Step 3 */} @@ -161,8 +180,12 @@ export default function BackgroundBoxesDemo() {
-

3. Get Actionable Insights

-

Receive clear, actionable insights and recommendations based on the AI analysis. Use these insights to make data-driven decisions and improve your business strategies.

+

+ 3. {t('steps.step3.title', 'Get Actionable Insights')} +

+

+ {t('steps.step3.description', 'Receive clear, actionable insights and recommendations based on the AI analysis. Use these insights to make data-driven decisions and improve your business strategies.')} +

@@ -172,7 +195,9 @@ export default function BackgroundBoxesDemo() { {/* Made by and Copyright */}
-
Copyright © {new Date().getFullYear()} asshelp All rights reserved.
+
+ {t('footer.copyright', 'Copyright')} © {new Date().getFullYear()} asshelp {t('footer.allRightsReserved', 'All rights reserved')}. +
); diff --git a/src/app/sign/page.tsx b/src/app/sign/page.tsx index bf4c8f5..b285395 100644 --- a/src/app/sign/page.tsx +++ b/src/app/sign/page.tsx @@ -4,6 +4,8 @@ import { Boxes } from "@/components/ui/background-boxes"; import SignupFormDemo from "@/components/signup"; import { AnimatePresence } from 'framer-motion'; import SignupPageSkeleton from "@/components/skeletons/signinPageSkeleton"; +import { NavbarDemo } from "@/components/nav"; +import { DotLottieReact } from "@lottiefiles/dotlottie-react"; export default function SignupPage() { const [isLoading, setIsLoading] = React.useState(true); @@ -26,7 +28,7 @@ export default function SignupPage() { return (
{/* Navbar */} - + {/* Loading Screen */} {isLoading && ( diff --git a/src/components/LanguageSwitcher.tsx b/src/components/LanguageSwitcher.tsx new file mode 100644 index 0000000..8b9d761 --- /dev/null +++ b/src/components/LanguageSwitcher.tsx @@ -0,0 +1,76 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { ChevronDown } from 'lucide-react'; +import { useLanguage } from '@/contexts/LanguageContext'; + +interface LanguageSwitcherProps { + className?: string; +} + +const languages = [ + { code: 'en', name: 'English', flag: '🇺🇸' }, + { code: 'fr', name: 'Français', flag: '🇫🇷' } +]; + +export default function LanguageSwitcher({ className = '' }: LanguageSwitcherProps) { + const [isOpen, setIsOpen] = useState(false); + const [selectedLang, setSelectedLang] = useState(languages[0]); + const { locale, setLocale } = useLanguage(); + + const handleLanguageChange = (lang: typeof languages[0]) => { + setSelectedLang(lang); + setIsOpen(false); + setLocale(lang.code); + }; + + useEffect(() => { + const lang = languages.find(l => l.code === locale); + if (lang) { + setSelectedLang(lang); + } + }, [locale]); + + return ( +
+ + + {isOpen && ( + <> + {/* Backdrop */} +
setIsOpen(false)} + /> + + {/* Dropdown */} +
+ {languages.map((lang) => ( + + ))} +
+ + )} +
+ ); +} diff --git a/src/components/LanguageSwitcherCompact.tsx b/src/components/LanguageSwitcherCompact.tsx new file mode 100644 index 0000000..ef1653b --- /dev/null +++ b/src/components/LanguageSwitcherCompact.tsx @@ -0,0 +1,74 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { Globe } from 'lucide-react'; +import { useLanguage } from '@/contexts/LanguageContext'; + +interface LanguageSwitcherCompactProps { + className?: string; +} + +const languages = [ + { code: 'en', name: 'English', flag: '🇺🇸' }, + { code: 'fr', name: 'Français', flag: '🇫🇷' } +]; + +export default function LanguageSwitcherCompact({ className = '' }: LanguageSwitcherCompactProps) { + const [isOpen, setIsOpen] = useState(false); + const [selectedLang, setSelectedLang] = useState(languages[0]); + const { locale, setLocale } = useLanguage(); + + const handleLanguageChange = (lang: typeof languages[0]) => { + setSelectedLang(lang); + setIsOpen(false); + setLocale(lang.code); + }; + + useEffect(() => { + const lang = languages.find(l => l.code === locale); + if (lang) { + setSelectedLang(lang); + } + }, [locale]); + + return ( +
+ + + {isOpen && ( + <> + {/* Backdrop */} +
setIsOpen(false)} + /> + + {/* Dropdown */} +
+ {languages.map((lang) => ( + + ))} +
+ + )} +
+ ); +} diff --git a/src/components/LanguageSwitcherMinimal.tsx b/src/components/LanguageSwitcherMinimal.tsx new file mode 100644 index 0000000..c268c0c --- /dev/null +++ b/src/components/LanguageSwitcherMinimal.tsx @@ -0,0 +1,72 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { useLanguage } from '@/contexts/LanguageContext'; + +interface LanguageSwitcherMinimalProps { + className?: string; +} + +const languages = [ + { code: 'en', name: 'English', flag: '🇺🇸' }, + { code: 'fr', name: 'Français', flag: '🇫🇷' } +]; + +export default function LanguageSwitcherMinimal({ className = '' }: LanguageSwitcherMinimalProps) { + const [isOpen, setIsOpen] = useState(false); + const [selectedLang, setSelectedLang] = useState(languages[0]); + const { locale, setLocale } = useLanguage(); + + const handleLanguageChange = (lang: typeof languages[0]) => { + setSelectedLang(lang); + setIsOpen(false); + setLocale(lang.code); + }; + + useEffect(() => { + const lang = languages.find(l => l.code === locale); + if (lang) { + setSelectedLang(lang); + } + }, [locale]); + + return ( +
+ + + {isOpen && ( + <> + {/* Backdrop */} +
setIsOpen(false)} + /> + + {/* Dropdown */} +
+ {languages.map((lang) => ( + + ))} +
+ + )} +
+ ); +} diff --git a/src/components/button2.tsx b/src/components/button2.tsx index 52009fe..7022748 100644 --- a/src/components/button2.tsx +++ b/src/components/button2.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslations } from '@/hooks/useTranslations'; interface Button2Props { disabled?: boolean; @@ -6,6 +7,7 @@ interface Button2Props { } const Button2: React.FC = ({ disabled = false, onClick }) => { + const { t } = useTranslations(); const buttonClasses = `flex justify-center gap-2 items-center shadow-xl text-lg lg:font-semibold border-gray-50 relative z-10 px-4 py-2 overflow-hidden border-2 rounded-full group ${ disabled ? 'opacity-50 cursor-not-allowed' : '' }`; @@ -25,7 +27,7 @@ const Button2: React.FC = ({ disabled = false, onClick }) => { }`} > - Next + {t('form.buttons.next', 'Next')} -
- +
+ +
+ +
{user === null && ( - router.push('/sign')}>Login + router.push('/sign')}> + {t('navigation.signIn', 'Login')} + )}
@@ -91,13 +99,16 @@ export function NavbarDemo() { ))}
+
+ +
{user === null && ( { setIsMobileMenuOpen(false); router.push('/sign'); }} variant="primary" className="w-full" > - Login + {t('navigation.signIn', 'Login')} )}
diff --git a/src/components/navbar1.tsx b/src/components/navbar1.tsx index 3d39e41..4d9335e 100644 --- a/src/components/navbar1.tsx +++ b/src/components/navbar1.tsx @@ -1,7 +1,6 @@ 'use client'; import React, { useState } from 'react'; -import Image from 'next/image'; import Link from 'next/link'; const Navbar = () => { diff --git a/src/components/ui/resizable-navbar.tsx b/src/components/ui/resizable-navbar.tsx index 7889ce4..bcc47f6 100644 --- a/src/components/ui/resizable-navbar.tsx +++ b/src/components/ui/resizable-navbar.tsx @@ -117,7 +117,7 @@ export const NavItems = ({ items, className, onItemClick }: NavItemsProps) => { setHovered(null)} className={cn( - "absolute inset-0 hidden flex-1 flex-row items-center justify-center space-x-2 text-sm font-medium text-zinc-600 transition duration-200 hover:text-zinc-800 lg:flex lg:space-x-2", + "absolute inset-0 hidden flex-1 flex-row items-center justify-start space-x-6 text-sm font-medium text-zinc-600 transition duration-200 hover:text-zinc-800 lg:flex lg:space-x-6 lg:pl-32 lg:pr-80", className, )} > diff --git a/src/contexts/LanguageContext.tsx b/src/contexts/LanguageContext.tsx new file mode 100644 index 0000000..a963a9e --- /dev/null +++ b/src/contexts/LanguageContext.tsx @@ -0,0 +1,46 @@ +'use client'; + +import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react'; + +interface LanguageContextType { + locale: string; + setLocale: (locale: string) => void; + isLoading: boolean; +} + +const LanguageContext = createContext(undefined); + +interface LanguageProviderProps { + children: ReactNode; +} + +export function LanguageProvider({ children }: LanguageProviderProps) { + const [locale, setLocaleState] = useState('en'); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + // Récupérer la langue depuis localStorage au chargement + const savedLocale = localStorage.getItem('preferred-language') || 'en'; + setLocaleState(savedLocale); + setIsLoading(false); + }, []); + + const setLocale = (newLocale: string) => { + setLocaleState(newLocale); + localStorage.setItem('preferred-language', newLocale); + }; + + return ( + + {children} + + ); +} + +export function useLanguage() { + const context = useContext(LanguageContext); + if (context === undefined) { + throw new Error('useLanguage must be used within a LanguageProvider'); + } + return context; +} diff --git a/src/hooks/useTranslations.ts b/src/hooks/useTranslations.ts new file mode 100644 index 0000000..fac9e6f --- /dev/null +++ b/src/hooks/useTranslations.ts @@ -0,0 +1,70 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useLanguage } from '@/contexts/LanguageContext'; + +interface Translations { + [key: string]: string | Translations; +} + +interface UseTranslationsReturn { + t: (key: string, fallback?: string) => string; + locale: string; + isLoading: boolean; +} + +export function useTranslations(): UseTranslationsReturn { + const { locale, isLoading: contextLoading } = useLanguage(); + const [translations, setTranslations] = useState({}); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const loadTranslations = async () => { + try { + const response = await fetch(`/locales/${locale}/common.json`); + if (!response.ok) { + throw new Error(`Failed to load translations for ${locale}`); + } + const data = await response.json(); + setTranslations(data); + } catch (error) { + console.error('Failed to load translations:', error); + // Fallback to English + try { + const response = await fetch('/locales/en/common.json'); + const data = await response.json(); + setTranslations(data); + } catch (fallbackError) { + console.error('Failed to load fallback translations:', fallbackError); + setTranslations({}); + } + } finally { + setIsLoading(false); + } + }; + + if (!contextLoading) { + loadTranslations(); + } + }, [locale, contextLoading]); + + const t = (key: string, fallback?: string): string => { + if (isLoading || contextLoading) return fallback || key; + + const keys = key.split('.'); + let value: string | Translations | undefined = translations; + + for (const k of keys) { + if (typeof value === 'object' && value !== null) { + value = value[k]; + } else { + value = undefined; + break; + } + } + + return (typeof value === 'string' ? value : fallback) || key; + }; + + return { t, locale, isLoading: isLoading || contextLoading }; +} diff --git a/src/lib/supabaseclient.ts b/src/lib/supabaseclient.ts index 39f02ea..ef3a53f 100644 --- a/src/lib/supabaseclient.ts +++ b/src/lib/supabaseclient.ts @@ -4,4 +4,4 @@ import { createClient } from '@supabase/supabase-js' const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL as string const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY as string -export const supabase = createClient(supabaseUrl, supabaseAnonKey) +export const supabase = createClient(supabaseUrl, supabaseAnonKey) \ No newline at end of file