Browse Source

feat: implement internationalization support, add translation files, and update components for language handling

master
sina_sajjadi 2 weeks ago
parent
commit
f5a17f1403
  1. 10
      next-i18next.config.js
  2. 41
      next.config.ts
  3. 146
      package-lock.json
  4. 3
      package.json
  5. 37
      public/locales/ar/common.json
  6. 38
      public/locales/en/common.json
  7. 2
      src/api/http.tsx
  8. 145
      src/components/language-switcher.tsx
  9. 10
      src/components/layout/header.tsx
  10. 10
      src/components/layout/mobile-navigation.tsx
  11. 18
      src/components/modals/audio-setting.tsx
  12. 20
      src/components/modals/languages-modal.tsx
  13. 8
      src/components/modals/reciters.tsx
  14. 42
      src/components/modals/search-modal.tsx
  15. 29
      src/components/modals/setting.tsx
  16. 19
      src/components/sidebar/list.tsx
  17. 19
      src/components/sidebar/tabs.tsx
  18. 11
      src/components/sticky-components/audio-controls.tsx
  19. 18
      src/components/sticky-components/download-app.tsx
  20. 17
      src/components/ui/search-duas.tsx
  21. 2
      src/components/utils/colorize-vowels.tsx
  22. 7
      src/pages/_app.tsx
  23. 11
      src/pages/about.tsx
  24. 11
      src/pages/duas/[slug].tsx
  25. 18
      src/pages/index.tsx
  26. 17
      tailwind.config.ts
  27. 2
      tsconfig.json

10
next-i18next.config.js

@ -0,0 +1,10 @@
module.exports = {
i18n: {
locales: [
'en', 'es', 'de', 'uz', 'pt', 'bn', 'zh', 'az', 'ur', 'fr', 'tr', 'id', 'sw', 'ru', 'ar', 'tg', 'fa', 'gu', 'ks', 'ha'
]
, // List the languages you support
defaultLocale: 'en', // Default language
},
reloadOnPrerender: true, // Ensures translations are reloaded on server-side rendering
};

41
next.config.ts

@ -1,5 +1,3 @@
// next.config.ts
import type { NextConfig } from "next"; import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
@ -7,13 +5,38 @@ const nextConfig: NextConfig = {
images: { images: {
domains: ["habibapp.com"], // Add the domain for image hosting domains: ["habibapp.com"], // Add the domain for image hosting
}, },
// typescript : {
// ignoreBuildErrors : true
// },
// eslint : {
// ignoreDuringBuilds : true
// }
// Add other Next.js config options here as needed
i18n: {
locales: [
"en",
"es",
"de",
"uz",
"pt",
"bn",
"zh",
"az",
"ur",
"fr",
"tr",
"id",
"sw",
"ru",
"ar",
"tg",
"fa",
"gu",
"ks",
"ha",
], // Add the locales you want to support
defaultLocale: "en", // Default language
},
// Uncomment these if you want to disable TypeScript build errors or ESLint during build
// typescript: {
// ignoreBuildErrors: true
// },
// eslint: {
// ignoreDuringBuilds: true
// }
}; };
export default nextConfig; export default nextConfig;

146
package-lock.json

@ -14,11 +14,14 @@
"classnames": "^2.5.1", "classnames": "^2.5.1",
"dompurify": "^3.2.3", "dompurify": "^3.2.3",
"framer-motion": "^11.15.0", "framer-motion": "^11.15.0",
"i18next": "^24.2.0",
"moment": "^2.30.1", "moment": "^2.30.1",
"moment-hijri": "^3.0.0", "moment-hijri": "^3.0.0",
"next": "15.1.0", "next": "15.1.0",
"next-i18next": "^15.4.1",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-i18next": "^15.4.0",
"react-icons": "^5.4.0" "react-icons": "^5.4.0"
}, },
"devDependencies": { "devDependencies": {
@ -47,6 +50,17 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/@babel/runtime": {
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@emnapi/runtime": { "node_modules/@emnapi/runtime": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
@ -1758,6 +1772,16 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true "dev": true
}, },
"node_modules/core-js": {
"version": "3.39.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz",
"integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==",
"hasInstallScript": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -3084,6 +3108,57 @@
"node": ">= 0.4" "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==",
"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==",
"dependencies": {
"void-elements": "3.1.0"
}
},
"node_modules/i18next": {
"version": "24.2.0",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.0.tgz",
"integrity": "sha512-ArJJTS1lV6lgKH7yEf4EpgNZ7+THl7bsGxxougPYiXRTJ/Fe1j08/TBpV9QsXCIYVfdE/HWG/xLezJ5DOlfBOA==",
"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"
}
],
"dependencies": {
"@babel/runtime": "^7.23.2"
},
"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=="
},
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@ -3932,6 +4007,40 @@
} }
} }
}, },
"node_modules/next-i18next": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/next-i18next/-/next-i18next-15.4.1.tgz",
"integrity": "sha512-n3cFqBKDpEDLXZVYD52H6k2QzLAnUSuBie02BmVpfywNZGxcNgq6QXdpVpvONQ7WkBVntPtldt867ZgtEcP1Og==",
"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"
}
],
"dependencies": {
"@babel/runtime": "^7.23.2",
"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": { "node_modules/next/node_modules/postcss": {
"version": "8.4.31", "version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@ -4453,6 +4562,27 @@
"react": "^19.0.0" "react": "^19.0.0"
} }
}, },
"node_modules/react-i18next": {
"version": "15.4.0",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.0.tgz",
"integrity": "sha512-Py6UkX3zV08RTvL6ZANRoBh9sL/ne6rQq79XlkHEdd82cZr2H9usbWpUNVadJntIZP2pu3M2rL1CN+5rQYfYFw==",
"dependencies": {
"@babel/runtime": "^7.25.0",
"html-parse-stringify": "^3.0.1"
},
"peerDependencies": {
"i18next": ">= 23.2.3",
"react": ">= 16.8.0"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/react-icons": { "node_modules/react-icons": {
"version": "5.4.0", "version": "5.4.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.4.0.tgz", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.4.0.tgz",
@ -4464,8 +4594,7 @@
"node_modules/react-is": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}, },
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
@ -4510,6 +4639,11 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/regexp.prototype.flags": { "node_modules/regexp.prototype.flags": {
"version": "1.5.3", "version": "1.5.3",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz",
@ -5424,6 +5558,14 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true "dev": true
}, },
"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==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

3
package.json

@ -15,11 +15,14 @@
"classnames": "^2.5.1", "classnames": "^2.5.1",
"dompurify": "^3.2.3", "dompurify": "^3.2.3",
"framer-motion": "^11.15.0", "framer-motion": "^11.15.0",
"i18next": "^24.2.0",
"moment": "^2.30.1", "moment": "^2.30.1",
"moment-hijri": "^3.0.0", "moment-hijri": "^3.0.0",
"next": "15.1.0", "next": "15.1.0",
"next-i18next": "^15.4.1",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-i18next": "^15.4.0",
"react-icons": "^5.4.0" "react-icons": "^5.4.0"
}, },
"devDependencies": { "devDependencies": {

37
public/locales/ar/common.json

@ -0,0 +1,37 @@
{
"home": "الرئيسية",
"about": "من نحن",
"donate": "تبرع",
"search_placeholder": "أدخل عنوان أو كلمة مفتاحية للبحث",
"loading": "جاري التحميل...",
"error_message": "فشل في جلب نتائج البحث.",
"close": "إغلاق",
"habib_app": "تطبيق حبيب",
"better_experience": "لتجربة أفضل",
"download": "تنزيل",
"timeline": "الجدول الزمني",
"settings": "الإعدادات",
"categories": "الفئات",
"famous": "الأشهر",
"near_by": "القريبين",
"today": "اليوم",
"todayDhikr": "ذكر اليوم",
"audio_settings": "إعدادات الصوت",
"reciters": "المقرئون",
"select_reciter": "اختر المقرئ",
"increase_speed": "زيادة السرعة",
"speed": "السرعة",
"choose_language": "اختر اللغة",
"loading_languages": "جاري تحميل اللغات...",
"failed_to_load_languages": "فشل في تحميل اللغات. الرجاء المحاولة مرة أخرى.",
"no_languages_available": "لا توجد لغات متاحة.",
"failed_to_load_reciters": "فشل في تحميل المقرئين. الرجاء المحاولة مرة أخرى.",
"search": "بحث",
"nothing_found": "لم يتم العثور على شيء!",
"dua_s_found": "تم العثور على دعاء(ات)",
"audio_available": "الصوت متاح",
"no_data_found": "لم يتم العثور على بيانات",
"arabic": "العربية",
"translation": "الترجمة",
"transliteration": "الترجمة الصوتية"
}

38
public/locales/en/common.json

@ -0,0 +1,38 @@
{
"home": "Home",
"about": "About us",
"donate": "Donate",
"search_placeholder": "Type a title or keyword to search",
"loading": "Loading...",
"error_message": "Failed to fetch search results.",
"close": "Close",
"habib_app": "Habib App",
"better_experience": "For Better Experience",
"download": "Download",
"timeline": "Time Line",
"settings": "Settings",
"categories": "Categories",
"famous": "Famous",
"near_by": "Near by",
"today": "Today",
"todayDhikr": "Today’s Dhikr",
"audio_settings": "Audio Settings",
"reciters": "Reciters",
"select_reciter": "Select Reciter",
"increase_speed": "Increase speed",
"speed": "Speed",
"choose_language": "Choose Language",
"loading_languages": "Loading languages...",
"failed_to_load_languages": "Failed to load languages. Please try again.",
"no_languages_available": "No languages available.",
"failed_to_load_reciters": "Failed to load reciters. Please try again.",
"search": "Search",
"nothing_found": "Nothing Found!",
"dua_s_found": "Dua(s) found",
"audio_available": "Audio Available",
"no_data_found": "No Data Found",
"arabic": "Arabic",
"translation": "Translation",
"transliteration": "Transliteration"
}

2
src/api/http.tsx

@ -6,7 +6,7 @@ if (typeof window !== 'undefined') {
locale = localStorage.getItem('locale') || 'en'; locale = localStorage.getItem('locale') || 'en';
} }
const http = axios.create({ const http = axios.create({
baseURL: 'https://habibapp.com',
baseURL: 'https://habibapp.com',
// headers: { // headers: {
// 'Authorization': 'Token 36d8be53bed5cab8027b66952b3f2c334cdca664', // 'Authorization': 'Token 36d8be53bed5cab8027b66952b3f2c334cdca664',
// }, // },

145
src/components/language-switcher.tsx

@ -1,26 +1,108 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
"use client"; "use client";
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
//@ts-ignore
import { IoGlobeSharp } from "react-icons/io5"; import { IoGlobeSharp } from "react-icons/io5";
import { IoIosArrowDown } from "react-icons/io"; import { IoIosArrowDown } from "react-icons/io";
import { useUI } from "./context/ui.context"; import { useUI } from "./context/ui.context";
import http from "@/api/http"; import http from "@/api/http";
import { useTranslation } from "next-i18next"; // Import the useTranslation hook
import { useRouter } from "next/router";
const data = [
{
"name": "English",
"code": "en"
},
{
"name": "Spanish",
"code": "es"
},
{
"name": "German",
"code": "de"
},
{
"name": "Uzbek",
"code": "uz"
},
{
"name": "Portuguese",
"code": "pt"
},
{
"name": "Bengali",
"code": "bn"
},
{
"name": "Chinese",
"code": "zh"
},
{
"name": "Azerbaijani",
"code": "az"
},
{
"name": "Urdu",
"code": "ur"
},
{
"name": "French",
"code": "fr"
},
{
"name": "Turkish",
"code": "tr"
},
{
"name": "Indonesian",
"code": "id"
},
{
"name": "Swahili",
"code": "sw"
},
{
"name": "Russian",
"code": "ru"
},
{
"name": "Arabic",
"code": "ar"
},
{
"name": "Tajik",
"code": "tg"
},
{
"name": "Persian",
"code": "fa"
},
{
"name": "Gujarati",
"code": "gu"
},
{
"name": "Kashmiri",
"code": "ks"
},
{
"name": "Hausa",
"code": "ha"
}
]
const LanguageSwitcher: React.FC = () => { const LanguageSwitcher: React.FC = () => {
const { t } = useTranslation('common'); // Initialize translation hook with 'common' namespace
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const { openModal } = useUI(); const { openModal } = useUI();
const router = useRouter();
const wrapperRef = useRef<HTMLDivElement | null>(null); const wrapperRef = useRef<HTMLDivElement | null>(null);
const [windowWidth, setWindowWidth] = useState( const [windowWidth, setWindowWidth] = useState(
typeof window !== "undefined" ? window.innerWidth : 0 typeof window !== "undefined" ? window.innerWidth : 0
); );
const [languages, setLanguages] = useState<{ code: string; name: string }[]>(
[]
);
const [languages, setLanguages] = useState<{ code: string; name: string }[]>([]);
const [selectedLanguage, setSelectedLanguage] = useState<string>("en"); const [selectedLanguage, setSelectedLanguage] = useState<string>("en");
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@ -33,22 +115,27 @@ const LanguageSwitcher: React.FC = () => {
}; };
const selectLanguage = (lang: string) => { const selectLanguage = (lang: string) => {
setSelectedLanguage(lang);
localStorage.setItem("locale", lang); // Save the selected language in localStorage
setIsOpen(false);
// Use router.replace() to update the URL with the new locale
router.push(router.asPath, undefined, { locale: lang }).then(() => {
// Optionally you can trigger the language change effect after the page is pushed
setSelectedLanguage(lang);
localStorage.setItem("locale", lang); // Save the selected language in localStorage
setIsOpen(false);
window.location.reload();
});
}; };
useEffect(() => { useEffect(() => {
const fetchLanguages = async () => { const fetchLanguages = async () => {
try {
setIsLoading(true);
const response = await http.get("v1/languages/");
setLanguages(response.data);
} catch (err) {
setError("Failed to load languages. Please try again.");
} finally {
setIsLoading(false);
}
// try {
// setIsLoading(true);
// const response = await http.get("v1/languages/");
setLanguages(data);
// } catch (err) {
// setError(t("error_message")); // Use translation for error message
// } finally {
// setIsLoading(false);
// }
}; };
fetchLanguages(); fetchLanguages();
@ -56,15 +143,12 @@ const LanguageSwitcher: React.FC = () => {
if (savedLanguage) { if (savedLanguage) {
setSelectedLanguage(savedLanguage); setSelectedLanguage(savedLanguage);
} }
}, []);
}, [t]); // Add t as a dependency to ensure translation is updated
useEffect(() => { useEffect(() => {
const handleResize = () => setWindowWidth(window.innerWidth); const handleResize = () => setWindowWidth(window.innerWidth);
const handleClickOutside = (event: MouseEvent) => { const handleClickOutside = (event: MouseEvent) => {
if (
wrapperRef.current &&
!wrapperRef.current.contains(event.target as Node)
) {
if (wrapperRef.current && !wrapperRef.current.contains(event.target as Node)) {
setIsOpen(false); setIsOpen(false);
} }
}; };
@ -77,6 +161,7 @@ const LanguageSwitcher: React.FC = () => {
document.removeEventListener("mousedown", handleClickOutside); document.removeEventListener("mousedown", handleClickOutside);
}; };
}, []); }, []);
console.log(languages);
return ( return (
<div className="relative inline-block font-sans" ref={wrapperRef}> <div className="relative inline-block font-sans" ref={wrapperRef}>
@ -91,9 +176,7 @@ const LanguageSwitcher: React.FC = () => {
<IoGlobeSharp color="black" size={25} className="mr-2" /> <IoGlobeSharp color="black" size={25} className="mr-2" />
{selectedLanguageName} {selectedLanguageName}
<span <span
className={`ml-2 transform transition-transform duration-200 ${
isOpen ? "rotate-180" : "rotate-0"
}`}
className={`ml-2 transform transition-transform duration-200 ${isOpen ? "rotate-180" : "rotate-0"}`}
> >
<IoIosArrowDown color="black" /> <IoIosArrowDown color="black" />
</span> </span>
@ -118,13 +201,13 @@ const LanguageSwitcher: React.FC = () => {
</ul> </ul>
)} )}
{isLoading && (
{isLoading && isOpen &&(
<div className="absolute left-0 mt-1 w-full bg-white border border-gray-200 rounded shadow-lg z-50 p-4 text-center"> <div className="absolute left-0 mt-1 w-full bg-white border border-gray-200 rounded shadow-lg z-50 p-4 text-center">
Loading...
{t("loading")} {/* Use translation for loading text */}
</div> </div>
)} )}
{error && (
{error && isOpen && (
<div className="absolute left-0 mt-1 w-full bg-white border border-gray-200 rounded shadow-lg z-50 p-4 text-red-500 text-center"> <div className="absolute left-0 mt-1 w-full bg-white border border-gray-200 rounded shadow-lg z-50 p-4 text-red-500 text-center">
{error} {error}
</div> </div>

10
src/components/layout/header.tsx

@ -1,11 +1,13 @@
import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import LanguageSwitcher from "../language-switcher"; import LanguageSwitcher from "../language-switcher";
import SearchDuas from "../ui/search-duas"; import SearchDuas from "../ui/search-duas";
import { useUI } from "../context/ui.context"; import { useUI } from "../context/ui.context";
import Logo from "../ui/logo"; import Logo from "../ui/logo";
import { useTranslation } from "next-i18next";
const Header = () => { const Header = () => {
const { displayDownload } = useUI(); const { displayDownload } = useUI();
const {t} = useTranslation("common")
return ( return (
<header <header
className={`w-full shadow-sm sticky top-0 bg-white hidden lg:flex z-10 ${ className={`w-full shadow-sm sticky top-0 bg-white hidden lg:flex z-10 ${
@ -18,14 +20,14 @@ const Header = () => {
<div className="h-full"> <div className="h-full">
<ul className="flex gap-11 font-semibold text-[#4D4D4D] h-full items-center"> <ul className="flex gap-11 font-semibold text-[#4D4D4D] h-full items-center">
<li className="h-full flex items-center border-[#c79389] hover:border-b hover:text-[#EB6E57] "> <li className="h-full flex items-center border-[#c79389] hover:border-b hover:text-[#EB6E57] ">
<Link href={"/"}>Home</Link>
<Link href={"/"}>{t("home")}</Link>
</li> </li>
<li className="h-full flex items-center border-[#F4846F] hover:border-b hover:text-[#EB6E57] "> <li className="h-full flex items-center border-[#F4846F] hover:border-b hover:text-[#EB6E57] ">
<Link href={"/about"}>About us</Link>
<Link href={"/about"}>{t("about")}</Link>
</li> </li>
<li className=""> <li className="">
<button className="bg-gradient-to-r from-[#F79B59] to-[#EB6E57] rounded-[28px] py-1 px-4"> <button className="bg-gradient-to-r from-[#F79B59] to-[#EB6E57] rounded-[28px] py-1 px-4">
<p className="text-white text-base font-semibold">Donate</p>
<p className="text-white text-base font-semibold">{t("donate")}</p>
</button> </button>
</li> </li>
</ul> </ul>

10
src/components/layout/mobile-navigation.tsx

@ -1,3 +1,5 @@
// components/MobileNavigation.tsx
import { useEffect } from "react"; import { useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion"; import { motion, AnimatePresence } from "framer-motion";
import { useUI } from "../context/ui.context"; import { useUI } from "../context/ui.context";
@ -5,6 +7,7 @@ import Logo from "../ui/logo";
import { IoMdClose } from "react-icons/io"; import { IoMdClose } from "react-icons/io";
import Link from "next/link"; import Link from "next/link";
import LanguageSwitcher from "../language-switcher"; import LanguageSwitcher from "../language-switcher";
import { useTranslation } from "next-i18next"; // Import useTranslation
const sidebarVariants = { const sidebarVariants = {
hidden: { x: "-100%" }, // Sidebar starts off-screen to the left hidden: { x: "-100%" }, // Sidebar starts off-screen to the left
@ -14,6 +17,7 @@ const sidebarVariants = {
const MobileNavigation = () => { const MobileNavigation = () => {
const { displaySidebar, closeSidebar } = useUI(); const { displaySidebar, closeSidebar } = useUI();
const { t } = useTranslation("common"); // Use the translation hook for common translations
useEffect(() => { useEffect(() => {
document.body.style.overflow = displaySidebar ? "hidden" : "auto"; document.body.style.overflow = displaySidebar ? "hidden" : "auto";
@ -52,17 +56,17 @@ const MobileNavigation = () => {
<div className="mt-14"> <div className="mt-14">
<ul className="flex flex-col gap-8 font-semibold text-black h-full items-start"> <ul className="flex flex-col gap-8 font-semibold text-black h-full items-start">
<li className="h-full flex items-center border-[#c79389] hover:border-b hover:text-[#EB6E57] "> <li className="h-full flex items-center border-[#c79389] hover:border-b hover:text-[#EB6E57] ">
<Link href={"/"}>Home</Link>
<Link href={"/"}>{t("home")}</Link> {/* Translated text */}
</li> </li>
<li className="h-full flex items-center border-[#F4846F] hover:border-b hover:text-[#EB6E57] "> <li className="h-full flex items-center border-[#F4846F] hover:border-b hover:text-[#EB6E57] ">
<Link href={"/about"}>About us</Link>
<Link href={"/about"}>{t("about")}</Link> {/* Translated text */}
</li> </li>
<li className=""> <li className="">
<Link <Link
href={"/"} href={"/"}
className="bg-gradient-to-r from-[#F79B59] to-[#EB6E57] text-transparent bg-clip-text font-semibold" className="bg-gradient-to-r from-[#F79B59] to-[#EB6E57] text-transparent bg-clip-text font-semibold"
> >
Donate
{t("donate")} {/* Translated text */}
</Link> </Link>
</li> </li>
</ul> </ul>

18
src/components/modals/audio-setting.tsx

@ -1,3 +1,5 @@
// components/AudioSetting.tsx
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { useUI } from "../context/ui.context"; import { useUI } from "../context/ui.context";
import { IoMdClose } from "react-icons/io"; import { IoMdClose } from "react-icons/io";
@ -5,6 +7,7 @@ import { IoIosArrowDown } from "react-icons/io";
import SpeedImg from "../../../public/assets/images/Group 852.svg"; import SpeedImg from "../../../public/assets/images/Group 852.svg";
import Image from "next/image"; import Image from "next/image";
import { useAudio } from "../context/audio-conext"; import { useAudio } from "../context/audio-conext";
import { useTranslation } from "next-i18next"; // Import useTranslation
interface ModalProps { interface ModalProps {
className?: string; // Optional className with a default value className?: string; // Optional className with a default value
@ -16,6 +19,7 @@ const AudioSetting: React.FC<ModalProps> = ({ className = "" }) => {
selectedReciter: { name: string } | null; selectedReciter: { name: string } | null;
audioRef: React.RefObject<HTMLAudioElement>; audioRef: React.RefObject<HTMLAudioElement>;
}; };
const { t } = useTranslation("common"); // Use the translation hook for common translations
const modalRef = useRef<HTMLDivElement>(null); const modalRef = useRef<HTMLDivElement>(null);
@ -61,21 +65,21 @@ const AudioSetting: React.FC<ModalProps> = ({ className = "" }) => {
aria-modal="true" aria-modal="true"
> >
<div className="flex justify-between items-center mb-5"> <div className="flex justify-between items-center mb-5">
<div className="text-[#8B8B8B] text-sm font-bold">Audio Settings</div>
<div className="text-[#8B8B8B] text-sm font-bold">{t("audio_settings")}</div> {/* Translated title */}
<button onClick={closeModal} aria-label="Close settings modal">
<button onClick={closeModal} aria-label={t("close")}>
<IoMdClose size={18} /> <IoMdClose size={18} />
</button> </button>
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="text-[#8B8B8B] text-xs font-bold">Reciters</div>
<div className="text-[#8B8B8B] text-xs font-bold">{t("reciters")}</div> {/* Translated label */}
<button <button
onClick={() => openModal("RECITERS_VIEW")} onClick={() => openModal("RECITERS_VIEW")}
className="p-3 border rounded-2xl flex justify-between items-center w-full" className="p-3 border rounded-2xl flex justify-between items-center w-full"
aria-label="Select a reciter"
aria-label={t("select_reciter")}
> >
<p>{selectedReciter?.name || "Select Reciter"}</p>
<p>{selectedReciter?.name || t("select_reciter")}</p> {/* Translated text */}
<IoIosArrowDown /> <IoIosArrowDown />
</button> </button>
</div> </div>
@ -83,11 +87,11 @@ const AudioSetting: React.FC<ModalProps> = ({ className = "" }) => {
<button <button
onClick={increaseSpeed} onClick={increaseSpeed}
className="bg-[#F4846F] flex gap-5 items-center p-4 rounded-2xl max-w-max" className="bg-[#F4846F] flex gap-5 items-center p-4 rounded-2xl max-w-max"
aria-label="Increase speed"
aria-label={t("increase_speed")}
> >
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<Image src={SpeedImg} alt="Speed" width={20} height={20} /> <Image src={SpeedImg} alt="Speed" width={20} height={20} />
<p className="text-white text-sm font-semibold">Speed</p>
<p className="text-white text-sm font-semibold">{t("speed")}</p> {/* Translated text */}
</div> </div>
<div className="bg-[#EB6E57] rounded-xl p-2"> <div className="bg-[#EB6E57] rounded-xl p-2">
<p className="text-white text-base font-semibold">{speed} x</p> <p className="text-white text-base font-semibold">{speed} x</p>

20
src/components/modals/languages-modal.tsx

@ -1,9 +1,8 @@
import http from "@/api/http";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { IoMdClose } from "react-icons/io"; import { IoMdClose } from "react-icons/io";
import { useUI } from "../context/ui.context"; import { useUI } from "../context/ui.context";
import { FaCheckCircle } from "react-icons/fa"; import { FaCheckCircle } from "react-icons/fa";
import { useTranslation } from "next-i18next"; // Import useTranslation
interface Language { interface Language {
code: string; // Assuming each language has a unique ID code: string; // Assuming each language has a unique ID
@ -16,6 +15,7 @@ const LanguageModal = () => {
const [isLoading, setIsLoading] = useState<boolean>(true); const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const { closeModal } = useUI(); const { closeModal } = useUI();
const { t } = useTranslation("common"); // Initialize useTranslation hook for common.json
useEffect(() => { useEffect(() => {
const fetchLanguages = async () => { const fetchLanguages = async () => {
@ -24,7 +24,7 @@ const LanguageModal = () => {
const response = await http.get("v1/languages/"); const response = await http.get("v1/languages/");
setLanguages(response.data); setLanguages(response.data);
} catch (err) { } catch (err) {
setError("Failed to load languages. Please try again.");
setError(t("failed_to_load_languages")); // Use translation for error message
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
@ -36,7 +36,7 @@ const LanguageModal = () => {
if (locale) { if (locale) {
setSelectedLanguage(locale); setSelectedLanguage(locale);
} }
}, []);
}, [t]); // Add t as dependency to ensure it updates with language change
const handleLanguageSelect = (code: string) => { const handleLanguageSelect = (code: string) => {
setSelectedLanguage(code); setSelectedLanguage(code);
@ -44,20 +44,20 @@ const LanguageModal = () => {
}; };
if (isLoading) { if (isLoading) {
return <div>Loading languages...</div>;
return <div>{t("loading_languages")}</div>; // Translated loading message
} }
if (error) { if (error) {
return <div className="text-red-500">{error}</div>;
return <div className="text-red-500">{error}</div>; // Display translated error
} }
return ( return (
<div className="p-4"> <div className="p-4">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h2 className="text-white font-bold">Choose Language</h2>
<h2 className="text-white font-bold">{t("choose_language")}</h2> {/* Translated title */}
<button <button
onClick={closeModal} onClick={closeModal}
aria-label="Close language modal"
aria-label={t("close")}
className="text-white" className="text-white"
> >
<IoMdClose size={18} /> <IoMdClose size={18} />
@ -77,13 +77,13 @@ const LanguageModal = () => {
}`} }`}
aria-selected={selectedLanguage === item.code} aria-selected={selectedLanguage === item.code}
> >
{selectedLanguage === item.code && <FaCheckCircle color="#F4846F" />}
{selectedLanguage === item.code && <FaCheckCircle color="#F4846F" />}
<p>{item.name}</p> <p>{item.name}</p>
</li> </li>
))} ))}
</ul> </ul>
) : ( ) : (
<p>No languages available.</p>
<p>{t("no_languages_available")}</p>
)} )}
</div> </div>
); );

8
src/components/modals/reciters.tsx

@ -1,4 +1,3 @@
// components/ui/setting.tsx
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { useUI } from "../context/ui.context"; import { useUI } from "../context/ui.context";
import { IoMdClose } from "react-icons/io"; import { IoMdClose } from "react-icons/io";
@ -6,6 +5,7 @@ import http from "@/api/http";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import Image from "next/image"; import Image from "next/image";
import { useAudio } from "../context/audio-conext"; import { useAudio } from "../context/audio-conext";
import { useTranslation } from "next-i18next"; // Import useTranslation
interface ModalProps { interface ModalProps {
className?: string; // Made optional with default value className?: string; // Made optional with default value
@ -24,6 +24,8 @@ const RecitersModal: React.FC<ModalProps> = ({ className = "" }) => {
const id = slug.split("-").pop(); const id = slug.split("-").pop();
const modalClasses = windowWidth < 1024 ? "" : "max-w-96 bottom-20 right-0"; const modalClasses = windowWidth < 1024 ? "" : "max-w-96 bottom-20 right-0";
const { t } = useTranslation("common"); // Initialize useTranslation hook for common.json
useEffect(() => { useEffect(() => {
http.get(`web/mafatih/${id}/reciters/`).then((res) => { http.get(`web/mafatih/${id}/reciters/`).then((res) => {
setReciters(res.data.results); setReciters(res.data.results);
@ -79,11 +81,11 @@ const RecitersModal: React.FC<ModalProps> = ({ className = "" }) => {
aria-modal="true" aria-modal="true"
> >
<div className="flex justify-between items-center p-6"> <div className="flex justify-between items-center p-6">
<div className="text-[#8B8B8B] text-sm font-bold">Reciter</div>
<div className="text-[#8B8B8B] text-sm font-bold">{t("reciters")}</div> {/* Translated title */}
<button <button
ref={closeButtonRef} ref={closeButtonRef}
onClick={closeModal} onClick={closeModal}
aria-label="Close modal"
aria-label={t("close")}
> >
<IoMdClose size={18} /> <IoMdClose size={18} />
</button> </button>

42
src/components/modals/search-modal.tsx

@ -3,14 +3,16 @@ import React, { useEffect, useState } from "react";
import search from "../../../public/assets/images/Search.svg"; import search from "../../../public/assets/images/Search.svg";
import { IoMdClose } from "react-icons/io"; import { IoMdClose } from "react-icons/io";
import { useUI } from "../context/ui.context"; import { useUI } from "../context/ui.context";
import http from "@/api/http"; // Assuming you have an http utility for API calls
import http from "@/api/http";
import Audio from "../../../public/assets/images/Icon ionic-md-musical-notes.svg"; import Audio from "../../../public/assets/images/Icon ionic-md-musical-notes.svg";
import NoData from "../../../public/assets/images/NoDataFound.svg"; import NoData from "../../../public/assets/images/NoDataFound.svg";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import DOMPurify from "dompurify"; // Install with `npm install dompurify`
import DOMPurify from "dompurify"; // Sanitize input to avoid XSS attacks
import { useTranslation } from "next-i18next"; // Import useTranslation
const SearchModal: React.FC = () => { const SearchModal: React.FC = () => {
const { closeModal } = useUI(); const { closeModal } = useUI();
const { t } = useTranslation("common"); // Use translation hook for common.json
const [value, setValue] = useState(""); const [value, setValue] = useState("");
const [results, setResults] = useState([]); const [results, setResults] = useState([]);
const [counts, setCounts] = useState(null); const [counts, setCounts] = useState(null);
@ -19,7 +21,7 @@ const SearchModal: React.FC = () => {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const router = useRouter(); const router = useRouter();
const openDua = (dua: Dua) => {
const openDua = (dua: any) => {
const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]"); const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]");
const updatedDuas = [...lastReadDuas, dua]; const updatedDuas = [...lastReadDuas, dua];
localStorage.setItem("last-read", JSON.stringify(updatedDuas)); localStorage.setItem("last-read", JSON.stringify(updatedDuas));
@ -32,6 +34,7 @@ const SearchModal: React.FC = () => {
setCounts(null); setCounts(null);
setResults([]); setResults([]);
setNoData(false); setNoData(false);
// If the input is empty, reset results and do not send a request // If the input is empty, reset results and do not send a request
if (value.trim() === "") { if (value.trim() === "") {
setResults([]); setResults([]);
@ -42,6 +45,7 @@ const SearchModal: React.FC = () => {
// Set loading state // Set loading state
setIsLoading(true); setIsLoading(true);
setError(null); setError(null);
// Implement debounce: wait for 500ms after the user stops typing // Implement debounce: wait for 500ms after the user stops typing
const debounceTimer = setTimeout(() => { const debounceTimer = setTimeout(() => {
// Perform the search API call with the actual user input // Perform the search API call with the actual user input
@ -57,28 +61,26 @@ const SearchModal: React.FC = () => {
}) })
.catch((err) => { .catch((err) => {
console.error("Error fetching search results:", err); console.error("Error fetching search results:", err);
setError("Failed to fetch search results.");
setError(t("error_message")); // Use translation for error message
setIsLoading(false); setIsLoading(false);
}); });
}, 500); // 500ms debounce duration }, 500); // 500ms debounce duration
// Cleanup function to cancel the timeout if the value changes before 500ms // Cleanup function to cancel the timeout if the value changes before 500ms
return () => clearTimeout(debounceTimer); return () => clearTimeout(debounceTimer);
}, [value]);
}, [value, t]);
return ( return (
<div className="bg-[#F5F5F5] w-full h-full overflow-y-auto"> <div className="bg-[#F5F5F5] w-full h-full overflow-y-auto">
<div className="flex flex-col bg-gray-200 p-6 gap-6"> <div className="flex flex-col bg-gray-200 p-6 gap-6">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<button <button
onClick={() => {
closeModal();
}}
onClick={() => closeModal()}
className="p-3 rounded-2xl bg-white" className="p-3 rounded-2xl bg-white"
> >
<IoMdClose size={18} /> <IoMdClose size={18} />
</button> </button>
<h3 className="text-base font-bold">Search</h3>
<h3 className="text-base font-bold">{t("search")}</h3> {/* Translated title */}
<div className="w-[43px]" /> <div className="w-[43px]" />
</div> </div>
<div className="flex w-full h-12 p-2 bg-white rounded-2xl focus-within:outline outline-[1px]"> <div className="flex w-full h-12 p-2 bg-white rounded-2xl focus-within:outline outline-[1px]">
@ -86,23 +88,21 @@ const SearchModal: React.FC = () => {
htmlFor="search-input" htmlFor="search-input"
className="flex items-center w-full h-full" className="flex items-center w-full h-full"
> >
<Image width={24} height={24} src={search} alt="Search" />
<Image width={24} height={24} src={search} alt={t("search_placeholder")} />
<input <input
value={value} value={value}
onChange={(e) => {
setValue(e.target.value);
}}
onChange={(e) => setValue(e.target.value)}
id="search-input" id="search-input"
className="w-full h-full ml-2 text-xs font-normal focus:outline-none" className="w-full h-full ml-2 text-xs font-normal focus:outline-none"
type="text" type="text"
placeholder="Type a title or keyword to search"
placeholder={t("search_placeholder")} // Use translation for placeholder
/> />
</label> </label>
</div> </div>
</div> </div>
<div className="p-4"> <div className="p-4">
{!!counts && ( {!!counts && (
<p className="text-xs font-normal">{counts} Dua(s) found</p>
<p className="text-xs font-normal">{counts} {t("dua_s_found")}</p>
)} )}
{results.map((item, index) => { {results.map((item, index) => {
// Function to highlight matching words // Function to highlight matching words
@ -132,16 +132,18 @@ const SearchModal: React.FC = () => {
</div> </div>
{item?.not_synced && ( {item?.not_synced && (
<div className="flex items-center p-3 bg-[#EBEBEB] rounded-lg"> <div className="flex items-center p-3 bg-[#EBEBEB] rounded-lg">
<Image src={Audio} alt="audio available" />
<Image src={Audio} alt={t("audio_available")} />
</div> </div>
)} )}
</div> </div>
); );
})} })}
{noData && <div className="flex flex-col text-[#4D4D4D] absolute top-1/2 left-[42%] items-center">
<Image src={NoData} alt="No data found"/>
<p className="text-sm font-semibold">Nothing Found!</p>
</div>}
{noData && (
<div className="flex flex-col text-[#4D4D4D] absolute top-1/2 left-[42%] items-center">
<Image src={NoData} alt={t("no_data_found")} />
<p className="text-sm font-semibold">{t("nothing_found")}</p>
</div>
)}
</div> </div>
</div> </div>
); );

29
src/components/modals/setting.tsx

@ -1,4 +1,3 @@
// components/ui/setting.tsx
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import { useUI } from "../context/ui.context"; import { useUI } from "../context/ui.context";
import Close from "../../../public/assets/images/Group 1000005170.svg"; import Close from "../../../public/assets/images/Group 1000005170.svg";
@ -6,6 +5,7 @@ import Image from "next/image";
import CheckBox from "../ui/check-box"; import CheckBox from "../ui/check-box";
import RangeInput from "../ui/range-input"; import RangeInput from "../ui/range-input";
import { useFontSettingsContext } from "../context/font-setting-context"; import { useFontSettingsContext } from "../context/font-setting-context";
import { useTranslation } from "next-i18next"; // Import the useTranslation hook
interface ModalProps { interface ModalProps {
className?: string; // Made optional with default value className?: string; // Made optional with default value
@ -17,6 +17,7 @@ const SettingModal: React.FC<ModalProps> = ({ className = "" }) => {
const closeButtonRef = useRef<HTMLButtonElement>(null); const closeButtonRef = useRef<HTMLButtonElement>(null);
const previouslyFocusedElement = useRef<HTMLElement | null>(null); const previouslyFocusedElement = useRef<HTMLElement | null>(null);
const { fontSettings, setFontSettings } = useFontSettingsContext(); const { fontSettings, setFontSettings } = useFontSettingsContext();
const { t } = useTranslation("common"); // Use translation hook
const modalClasses = const modalClasses =
modalView === "SETTING_VIEW" ? "absolute w-full bottom-0" : ""; modalView === "SETTING_VIEW" ? "absolute w-full bottom-0" : "";
@ -24,12 +25,10 @@ const SettingModal: React.FC<ModalProps> = ({ className = "" }) => {
// Handle modal visibility and accessibility // Handle modal visibility and accessibility
useEffect(() => { useEffect(() => {
const handleOutsideClick = (event: MouseEvent) => { const handleOutsideClick = (event: MouseEvent) => {
console.log(modalRef.current , modalRef.current.contains(event.target as Node));
if ( if (
modalRef.current && modalRef.current &&
!modalRef.current.contains(event.target as Node) !modalRef.current.contains(event.target as Node)
) { ) {
if (modalView === "SETTING_VIEW") { if (modalView === "SETTING_VIEW") {
closeModal(); closeModal();
} else { } else {
@ -71,21 +70,16 @@ const SettingModal: React.FC<ModalProps> = ({ className = "" }) => {
}; };
}, [displaySetting, closeSetting]); }, [displaySetting, closeSetting]);
console.log(
!displaySetting || !(modalView === "SETTING_VIEW"),
displaySetting,
modalView
);
if (!displaySetting && !(modalView === "SETTING_VIEW")) { if (!displaySetting && !(modalView === "SETTING_VIEW")) {
return null; return null;
} }
// Handlers for toggles // Handlers for toggles
const handleArabicToggle = () => { const handleArabicToggle = () => {
setFontSettings((prev) => ({ setFontSettings((prev) => ({
...prev, ...prev,
arabic: !prev.arabic, arabic: !prev.arabic,
})); }));
// Additional logic if needed
}; };
const handleTranslationToggle = () => { const handleTranslationToggle = () => {
@ -93,7 +87,6 @@ const SettingModal: React.FC<ModalProps> = ({ className = "" }) => {
...prev, ...prev,
translation: !prev.translation, translation: !prev.translation,
})); }));
// Additional logic if needed
}; };
const handleTransliterationToggle = () => { const handleTransliterationToggle = () => {
@ -101,7 +94,6 @@ const SettingModal: React.FC<ModalProps> = ({ className = "" }) => {
...prev, ...prev,
transliteration: !prev.transliteration, transliteration: !prev.transliteration,
})); }));
// Additional logic if needed
}; };
// Handlers for range inputs // Handlers for range inputs
@ -110,7 +102,6 @@ const SettingModal: React.FC<ModalProps> = ({ className = "" }) => {
...prev, ...prev,
arabicRange: value, arabicRange: value,
})); }));
// Additional logic if needed
}; };
const handleTranslationRangeChange = (value: number) => { const handleTranslationRangeChange = (value: number) => {
@ -118,7 +109,6 @@ const SettingModal: React.FC<ModalProps> = ({ className = "" }) => {
...prev, ...prev,
translationRange: value, translationRange: value,
})); }));
// Additional logic if needed
}; };
const handleTransliterationRangeChange = (value: number) => { const handleTransliterationRangeChange = (value: number) => {
@ -126,7 +116,6 @@ const SettingModal: React.FC<ModalProps> = ({ className = "" }) => {
...prev, ...prev,
transliterationRange: value, transliterationRange: value,
})); }));
// Additional logic if needed
}; };
return ( return (
@ -137,7 +126,7 @@ const SettingModal: React.FC<ModalProps> = ({ className = "" }) => {
aria-modal="true" aria-modal="true"
> >
<div className="flex justify-between"> <div className="flex justify-between">
<p className="text-[#8B8B8B] text-sm font-bold">Settings</p>
<p className="text-[#8B8B8B] text-sm font-bold">{t("settings")}</p> {/* Translated title */}
<button <button
ref={closeButtonRef} ref={closeButtonRef}
onClick={() => { onClick={() => {
@ -145,16 +134,16 @@ const SettingModal: React.FC<ModalProps> = ({ className = "" }) => {
closeModal(); closeModal();
}} }}
className="focus:outline-none" className="focus:outline-none"
aria-label="Close Settings"
aria-label={t("close")} // Translated label
> >
<Image src={Close} alt="Close" />
<Image src={Close} alt={t("close")} />
</button> </button>
</div> </div>
<div className="flex flex-col gap-6 justify-between mb-4 text-sm font-normal"> <div className="flex flex-col gap-6 justify-between mb-4 text-sm font-normal">
{/* Arabic Toggle */} {/* Arabic Toggle */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<label htmlFor="arabic-toggle" className="text-gray-700 font-medium"> <label htmlFor="arabic-toggle" className="text-gray-700 font-medium">
Arabic
{t("arabic")} {/* Translated Arabic label */}
</label> </label>
<div className="flex gap-5 items-center"> <div className="flex gap-5 items-center">
<CheckBox <CheckBox
@ -176,7 +165,7 @@ const SettingModal: React.FC<ModalProps> = ({ className = "" }) => {
htmlFor="translation-toggle" htmlFor="translation-toggle"
className="text-gray-700 font-medium" className="text-gray-700 font-medium"
> >
Translation
{t("translation")} {/* Translated Translation label */}
</label> </label>
<div className="flex gap-5 items-center"> <div className="flex gap-5 items-center">
<CheckBox <CheckBox
@ -198,7 +187,7 @@ const SettingModal: React.FC<ModalProps> = ({ className = "" }) => {
htmlFor="transliteration-toggle" htmlFor="transliteration-toggle"
className="text-gray-700 font-medium" className="text-gray-700 font-medium"
> >
Transliteration
{t("transliteration")} {/* Translated Transliteration label */}
</label> </label>
<div className="flex gap-5 items-center"> <div className="flex gap-5 items-center">
<CheckBox <CheckBox

19
src/components/sidebar/list.tsx

@ -13,8 +13,10 @@ import { useRouter } from "next/navigation";
import colorizeVowels from "../utils/colorize-vowels"; import colorizeVowels from "../utils/colorize-vowels";
import { formatHijriDate } from "../utils/date-formaters"; import { formatHijriDate } from "../utils/date-formaters";
import { Category, Dua, ListProps } from "../utils/types"; import { Category, Dua, ListProps } from "../utils/types";
import { useTranslation } from "next-i18next"; // Importing the translation hook
const List: React.FC<ListProps> = ({ tab, path, setPath, data, setData }) => { const List: React.FC<ListProps> = ({ tab, path, setPath, data, setData }) => {
const { t } = useTranslation("common"); // Initialize translation hook for "common" namespace
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [currentDhikr, setCurrentDhikr] = useState<string>(""); const [currentDhikr, setCurrentDhikr] = useState<string>("");
const [currentTranslation, setCurrentTranslation] = useState<string>(""); const [currentTranslation, setCurrentTranslation] = useState<string>("");
@ -23,8 +25,9 @@ const List: React.FC<ListProps> = ({ tab, path, setPath, data, setData }) => {
const dayOfWeek = new Intl.DateTimeFormat("en-US", { const dayOfWeek = new Intl.DateTimeFormat("en-US", {
weekday: "long", weekday: "long",
}).format(today); }).format(today);
let locale : string ;
// Mapping of days of the week to their respective Dhikr phrases with vowels and English translations
let locale: string;
useEffect(() => { useEffect(() => {
locale = localStorage.getItem("locale") || "en"; locale = localStorage.getItem("locale") || "en";
if (tab === "Today") { if (tab === "Today") {
@ -106,8 +109,6 @@ let locale : string ;
if (!uniqueDuasMap.has(dua.id)) { if (!uniqueDuasMap.has(dua.id)) {
uniqueDuasMap.set(dua.id, dua); uniqueDuasMap.set(dua.id, dua);
} }
// If a duplicate is found, you can decide to keep the first occurrence
// or implement additional logic here
}); });
// Convert the Map back to an array of unique duas // Convert the Map back to an array of unique duas
@ -128,19 +129,15 @@ let locale : string ;
`Error fetching duas for category ${category.id}:`, `Error fetching duas for category ${category.id}:`,
error error
); );
// Handle errors as needed
}); });
}); });
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [path]); }, [path]);
if (loading) { if (loading) {
return <p>Loading ...</p>;
return <p>{t("loading")}</p>; {/* Translating the loading message */}
} }
console.log(data);
return ( return (
<div> <div>
{tab === "Today" && ( {tab === "Today" && (
@ -157,8 +154,8 @@ let locale : string ;
className="rounded-full w-full h-full object-cover" className="rounded-full w-full h-full object-cover"
/> />
<p className="absolute text-center text-[#EE755F] font-medium text-sm"> <p className="absolute text-center text-[#EE755F] font-medium text-sm">
{dayOfWeek}&apos;s Dhikr
</p>
{t("todayDhikr")}
</p> {/* Translating the title */}
</div> </div>
</div> </div>

19
src/components/sidebar/tabs.tsx

@ -1,19 +1,18 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
"use client";
import { useState } from "react"; import { useState } from "react";
import List from "./list"; import List from "./list";
//@ts-ignore
import { FaArrowLeft } from "react-icons/fa6"; import { FaArrowLeft } from "react-icons/fa6";
import { DataState,Path } from "@/components/utils/types"; // Import types from types.ts
import { DataState, Path } from "@/components/utils/types"; // Import types from types.ts
import { useTranslation } from "next-i18next"; // Importing useTranslation
const tabs = [ const tabs = [
{ name: "Categories" }, { name: "Categories" },
{ name: "Famous" }, { name: "Famous" },
{ name: "Near by" },
{ name: "near_by" },
{ name: "Today" }, { name: "Today" },
]; ];
const Tabs = () => { const Tabs = () => {
const { t } = useTranslation("common"); // Initialize the translation hook
const [selected, setSelected] = useState({ name: "Today" }); const [selected, setSelected] = useState({ name: "Today" });
const [data, setData] = useState<DataState>({ type: null, data: [] }); const [data, setData] = useState<DataState>({ type: null, data: [] });
const [path, setPath] = useState<Path[] | []>([]); const [path, setPath] = useState<Path[] | []>([]);
@ -38,12 +37,11 @@ const Tabs = () => {
}); });
// Optionally, you can update the data here as well: // Optionally, you can update the data here as well:
setData({type : path[index].type , data :path[index].data });
setData({ type: path[index].type, data: path[index].data });
}; };
return ( return (
<> <>
{" "}
{path.length > 1 && ( {path.length > 1 && (
<div> <div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@ -91,14 +89,13 @@ const Tabs = () => {
<li <li
key={tab.name} key={tab.name}
className={`hover:bg-white hover:shadow-lg w-20 h-9 rounded-xl flex items-center justify-center cursor-pointer ${ className={`hover:bg-white hover:shadow-lg w-20 h-9 rounded-xl flex items-center justify-center cursor-pointer ${
JSON.stringify(selected) === JSON.stringify(tab) &&
"bg-white shadow-lg"
JSON.stringify(selected) === JSON.stringify(tab) && "bg-white shadow-lg"
}`} }`}
onClick={() => { onClick={() => {
setSelected(tab); setSelected(tab);
}} }}
> >
<p>{tab.name}</p>
<p>{t(tab.name.toLowerCase())}</p> {/* Translate tab name */}
</li> </li>
))} ))}
</ul> </ul>

11
src/components/sticky-components/audio-controls.tsx

@ -8,6 +8,7 @@ import { useUI } from "../context/ui.context";
import { useAudio } from "../context/audio-conext"; import { useAudio } from "../context/audio-conext";
import { IoPersonSharp } from "react-icons/io5"; import { IoPersonSharp } from "react-icons/io5";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { useTranslation } from "next-i18next"; // Importing the useTranslation hook
const AudioControls = () => { const AudioControls = () => {
const { openModal, closeAudio } = useUI(); const { openModal, closeAudio } = useUI();
@ -18,6 +19,9 @@ const AudioControls = () => {
const params = useParams(); const params = useParams();
const slug = params?.slug as string; const slug = params?.slug as string;
// Initialize the translation hook
const { t } = useTranslation("common");
const play = () => { const play = () => {
if (audioRef.current && audio) { if (audioRef.current && audio) {
if (lastPart && audio.audio_sync_data.length > 0) { if (lastPart && audio.audio_sync_data.length > 0) {
@ -90,6 +94,7 @@ const AudioControls = () => {
return result; return result;
} }
useEffect(() => { useEffect(() => {
if (audioRef.current) { if (audioRef.current) {
audioRef.current.addEventListener("timeupdate", onTimeUpdate); audioRef.current.addEventListener("timeupdate", onTimeUpdate);
@ -115,7 +120,7 @@ const AudioControls = () => {
height={58} height={58}
className="absolute right bottom-4" className="absolute right bottom-4"
src={timeLine} src={timeLine}
alt="Time Line"
alt={t("timeline")} // Translate the alt text for Time Line
/> />
<div <div
className="absolute w-full h-full" className="absolute w-full h-full"
@ -155,7 +160,7 @@ const AudioControls = () => {
</div> </div>
<div className="flex gap-4 items-center"> <div className="flex gap-4 items-center">
<button onClick={() => openModal("AUDIO_SETTING_VIEW")}> <button onClick={() => openModal("AUDIO_SETTING_VIEW")}>
<Image src={Setting} alt="Setting" />
<Image src={Setting} alt={t("settings")} /> {/* Translate "Setting" */}
</button> </button>
<button <button
onClick={() => { onClick={() => {
@ -163,7 +168,7 @@ const AudioControls = () => {
pause(); pause();
}} }}
> >
<Image src={Close} alt="Setting" />
<Image src={Close} alt={t("close")} /> {/* Translate "Close" */}
</button> </button>
</div> </div>
</div> </div>

18
src/components/sticky-components/download-app.tsx

@ -3,34 +3,36 @@ import Close from "../../../public/assets/images/Group 1000005170.svg";
import Habib from "../../../public/assets/images/Group 1000005169.svg"; import Habib from "../../../public/assets/images/Group 1000005169.svg";
import Image from "next/image"; import Image from "next/image";
import { useUI } from "../context/ui.context"; import { useUI } from "../context/ui.context";
import { useTranslation } from "next-i18next"; // Import the useTranslation hook
const DownloadApp: React.FC = () => { const DownloadApp: React.FC = () => {
const { closeDownload, displayDownload , displayAudio } = useUI();
const { closeDownload, displayDownload, displayAudio } = useUI();
console.log(displayDownload);
// Initialize the translation hook
const { t } = useTranslation("common"); // 'common' is the namespace you are using for translations
return ( return (
<div <div
className={`flex fixed bottom-3 left-0 right-0 mx-3 p-2 justify-between items-center bg-white rounded-2xl shadow-lg z-50 lg:flex-row-reverse lg:px-11 lg:top-0 lg:bottom-auto lg:mx-0 lg:rounded-none ${displayAudio && "mb-[88px]"}`} className={`flex fixed bottom-3 left-0 right-0 mx-3 p-2 justify-between items-center bg-white rounded-2xl shadow-lg z-50 lg:flex-row-reverse lg:px-11 lg:top-0 lg:bottom-auto lg:mx-0 lg:rounded-none ${displayAudio && "mb-[88px]"}`}
> >
<button className="" onClick={closeDownload}>
<Image src={Close} alt="Close" />
<button onClick={closeDownload}>
<Image src={Close} alt={t("close")} /> {/* You can translate the "Close" alt text if needed */}
</button> </button>
<div className="flex gap-14 items-center"> <div className="flex gap-14 items-center">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Image src={Habib} alt="Habib" /> <Image src={Habib} alt="Habib" />
<div> <div>
<p className="text-lg font-bold">Habib App</p>
<p className="text-lg font-bold">{t("habib_app")}</p> {/* Translate "Habib App" */}
<p className="text-[12px] font-semibold -mt-1"> <p className="text-[12px] font-semibold -mt-1">
For Better Experience
{t("better_experience")} {/* Translate "For Better Experience" */}
</p> </p>
</div> </div>
</div> </div>
<button className="bg-black w-24 h-9 rounded-lg"> <button className="bg-black w-24 h-9 rounded-lg">
<p className="text-white font-Calibri">Download</p>
<p className="text-white font-Calibri">{t("download")}</p> {/* Translate "Download" */}
</button> </button>
</div> </div>
<div className="hidden sm:block"/>
<div className="hidden sm:block" />
</div> </div>
); );
}; };

17
src/components/ui/search-duas.tsx

@ -1,9 +1,8 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import React, { useEffect, useState, ChangeEvent, useRef } from "react"; import React, { useEffect, useState, ChangeEvent, useRef } from "react";
import http from "@/api/http"; import http from "@/api/http";
//@ts-ignore
import { HiMiniMagnifyingGlass } from "react-icons/hi2"; import { HiMiniMagnifyingGlass } from "react-icons/hi2";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useTranslation } from "next-i18next"; // Import useTranslation hook
interface Dua { interface Dua {
id: number; id: number;
@ -12,6 +11,7 @@ interface Dua {
} }
const SearchDuas: React.FC = () => { const SearchDuas: React.FC = () => {
const { t } = useTranslation('common'); // Initialize translation hook with the 'common' namespace
const router = useRouter(); const router = useRouter();
const [value, setValue] = useState<string>(""); const [value, setValue] = useState<string>("");
const [results, setResults] = useState<Dua[]>([]); const [results, setResults] = useState<Dua[]>([]);
@ -23,7 +23,7 @@ const SearchDuas: React.FC = () => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const openDua = (dua: Dua) => { const openDua = (dua: Dua) => {
setShow(false)
setShow(false);
const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]"); const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]");
const updatedDuas = [...lastReadDuas, dua]; const updatedDuas = [...lastReadDuas, dua];
localStorage.setItem("last-read", JSON.stringify(updatedDuas)); localStorage.setItem("last-read", JSON.stringify(updatedDuas));
@ -57,14 +57,14 @@ const SearchDuas: React.FC = () => {
}) })
.catch((err) => { .catch((err) => {
console.error("Error fetching search results:", err); console.error("Error fetching search results:", err);
setError("Failed to fetch search results.");
setError(t("error_message")); // Use translation for error message
setIsLoading(false); setIsLoading(false);
}); });
}, 500); // 500ms debounce duration }, 500); // 500ms debounce duration
// Cleanup function to cancel the timeout if the value changes before 500ms // Cleanup function to cancel the timeout if the value changes before 500ms
return () => clearTimeout(debounceTimer); return () => clearTimeout(debounceTimer);
}, [value]);
}, [value, t]); // Add t to the dependency array
// Handle input changes // Handle input changes
const handleChange = (e: ChangeEvent<HTMLInputElement>) => { const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
@ -102,7 +102,7 @@ const SearchDuas: React.FC = () => {
id="search-input" id="search-input"
type="text" type="text"
value={value} value={value}
placeholder="Type a title or keyword to search"
placeholder={t("search_placeholder")} // Use translation for placeholder
className="text-xs bg-[#EBEBEB] w-full focus:outline-none" className="text-xs bg-[#EBEBEB] w-full focus:outline-none"
aria-label="Search Duas" aria-label="Search Duas"
onClick={() => { onClick={() => {
@ -114,7 +114,7 @@ const SearchDuas: React.FC = () => {
{/* Loading Indicator */} {/* Loading Indicator */}
{isLoading && show && ( {isLoading && show && (
<div className="absolute top-14 mt-2 w-64 z-20 bg-white shadow-lg rounded-lg text-sm text-gray-600 px-4 py-2"> <div className="absolute top-14 mt-2 w-64 z-20 bg-white shadow-lg rounded-lg text-sm text-gray-600 px-4 py-2">
Loading...
{t("loading")} {/* Use translation for loading text */}
</div> </div>
)} )}
@ -128,10 +128,9 @@ const SearchDuas: React.FC = () => {
<ul className="absolute top-14 mt-2 w-64 z-20 bg-white shadow-lg rounded-lg h-60 overflow-y-auto"> <ul className="absolute top-14 mt-2 w-64 z-20 bg-white shadow-lg rounded-lg h-60 overflow-y-auto">
{results.map((dua) => ( {results.map((dua) => (
<li <li
onClick={()=>openDua(dua)}
onClick={() => openDua(dua)}
key={dua.id} key={dua.id}
className="px-4 py-2 hover:bg-gray-100 cursor-pointer" className="px-4 py-2 hover:bg-gray-100 cursor-pointer"
// Add onClick handler or link if needed
> >
{dua.title} {dua.title}
</li> </li>

2
src/components/utils/colorize-vowels.tsx

@ -7,7 +7,7 @@ const colorizeVowels = (text: string) => {
str.replace(/[\u064B-\u065F\u0670]/g, ""); // Remove diacritics from the text str.replace(/[\u064B-\u065F\u0670]/g, ""); // Remove diacritics from the text
return ( return (
<div className="relative">
<div className="relative text-rendering-optimize-speed">
{/* Bottom layer: Full text with vowels in orange */} {/* Bottom layer: Full text with vowels in orange */}
<div className="absolute top-0 right-0 text-[#EB6E57] font-[UthmanTaha]"> <div className="absolute top-0 right-0 text-[#EB6E57] font-[UthmanTaha]">
{normalizedText} {normalizedText}

7
src/pages/_app.tsx

@ -6,12 +6,12 @@ import MobileHeader from "@/components/layout/mobile-header";
import MobileNavigation from "@/components/layout/mobile-navigation"; import MobileNavigation from "@/components/layout/mobile-navigation";
import SideBar from "@/components/layout/sidebar"; import SideBar from "@/components/layout/sidebar";
import ManagedModal from "@/components/modals/modal-manager"; import ManagedModal from "@/components/modals/modal-manager";
import DownloadApp from "@/components/sticky-components/download-app";
import StickyComponents from "@/components/sticky-components/sticky-components"; import StickyComponents from "@/components/sticky-components/sticky-components";
import "@/styles/globals.css"; import "@/styles/globals.css";
import { appWithTranslation } from "next-i18next";
import type { AppProps } from "next/app"; import type { AppProps } from "next/app";
export default function App({ Component, pageProps }: AppProps) {
function App({ Component, pageProps }: AppProps) {
console.log(pageProps); console.log(pageProps);
return ( return (
<FontSettingsProvider> <FontSettingsProvider>
@ -35,3 +35,6 @@ export default function App({ Component, pageProps }: AppProps) {
</FontSettingsProvider> </FontSettingsProvider>
); );
} }
export default appWithTranslation(App);

11
src/pages/about.tsx

@ -1,5 +1,6 @@
import Image from "next/image"; import Image from "next/image";
import img from "../../public/assets/images/jamkaran.png"; import img from "../../public/assets/images/jamkaran.png";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
const About = () => { const About = () => {
return ( return (
@ -34,4 +35,14 @@ const About = () => {
</div> </div>
); );
}; };
export async function getServerSideProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])), // Load translations for 'common' namespace
},
};
}
export default About; export default About;

11
src/pages/duas/[slug].tsx

@ -14,6 +14,9 @@ import SettingModal from "@/components/modals/setting";
import { useUI } from "@/components/context/ui.context"; import { useUI } from "@/components/context/ui.context";
import { useFontSettingsContext } from "@/components/context/font-setting-context"; import { useFontSettingsContext } from "@/components/context/font-setting-context";
import { useAudio } from "@/components/context/audio-conext"; import { useAudio } from "@/components/context/audio-conext";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
// Define the Dua interface // Define the Dua interface
interface Dua { interface Dua {
@ -321,4 +324,12 @@ const DuaComponent: React.FC<DuaComponentProps> = ({
); );
}; };
export async function getServerSideProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])), // Load translations for 'common' namespace
},
};
}
export default DuaComponent; export default DuaComponent;

18
src/pages/index.tsx

@ -1,12 +1,22 @@
import Image from "next/image"; import Image from "next/image";
import NoData from "../../public/assets/images/Untitled-1-02.svg";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import NoData from "../../public/assets/images/Untitled-1-02.svg"; // Ensure this is the correct path
export default function Home() { export default function Home() {
// This is where you can use translations, if necessary
// const { t } = useTranslation('common'); // Uncomment if you need to use translations in this page
return ( return (
<div className={`flex-grow w-full items-center justify-center h-[calc(100vh-130px)] bg-[#F5F5F5] lg:p-6 lg:rounded-3xl hidden lg:flex`}> <div className={`flex-grow w-full items-center justify-center h-[calc(100vh-130px)] bg-[#F5F5F5] lg:p-6 lg:rounded-3xl hidden lg:flex`}>
<Image src={NoData} alt="no data"/>
<Image src={NoData} alt="No data" /> {/* Ensure `NoData` is a valid imported image */}
</div> </div>
); );
} }
export async function getServerSideProps({ locale }: { locale: string }) {
return {
props: {
...(await serverSideTranslations(locale, ['common'])), // Load translations for 'common' namespace
},
};
}

17
tailwind.config.ts

@ -13,13 +13,24 @@ export default {
'inner-header': '2px -20px 15px 30px rgba(244,132,111,1);', 'inner-header': '2px -20px 15px 30px rgba(244,132,111,1);',
}, },
fontFamily: { fontFamily: {
Calibri : ["Calibri"]
} ,
Calibri: ["Calibri"],
},
colors: { colors: {
background: "var(--background)", background: "var(--background)",
foreground: "var(--foreground)", foreground: "var(--foreground)",
}, },
}, },
}, },
plugins: [],
plugins: [
function ({ addUtilities }) {
addUtilities(
{
'.text-rendering-optimize-speed': {
'text-rendering': 'optimizeSpeed',
},
},
['responsive', 'hover'] // Make this available for responsive and hover variants
);
},
],
} satisfies Config; } satisfies Config;

2
tsconfig.json

@ -17,6 +17,6 @@
"@/*": ["./src/*"] "@/*": ["./src/*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next-i18next.config.js"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }
Loading…
Cancel
Save