diff --git a/package-lock.json b/package-lock.json index 9e20599..b38db3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,14 @@ "@emailjs/browser": "^4.3.3", "@headlessui/react": "^1.7.18", "@heroicons/react": "^2.1.3", + "i18next": "^23.11.5", + "i18next-browser-languagedetector": "^8.0.0", + "i18next-http-backend": "^2.5.2", "localforage": "^1.10.0", "match-sorter": "^6.3.4", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^14.1.2", "react-icons": "^5.0.1", "react-router-dom": "^6.22.3", "react-toastify": "^10.0.5", @@ -1990,6 +1994,14 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2729,6 +2741,52 @@ "node": ">= 0.4" } }, + "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": "23.11.5", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.5.tgz", + "integrity": "sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==", + "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" + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz", + "integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-http-backend": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.5.2.tgz", + "integrity": "sha512-+K8HbDfrvc1/2X8jpb7RLhI9ZxBDpx3xogYkQwGKlWAUXLSEGXzgdt3EcUjLlBCdMwdQY+K+EUF6oh8oB6rwHw==", + "dependencies": { + "cross-fetch": "4.0.0" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -3138,6 +3196,25 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -3561,6 +3638,27 @@ "react": "^18.2.0" } }, + "node_modules/react-i18next": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.2.tgz", + "integrity": "sha512-FSIcJy6oauJbGEXfhUgVeLzvWBhIBIS+/9c6Lj4niwKZyGaGb4V4vUbATXSlsHJDXXB+ociNxqFNiFuV1gmoqg==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "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": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz", @@ -4113,6 +4211,11 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/ts-api-utils": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", @@ -4268,6 +4371,28 @@ } } }, + "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/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index ddcf0e7..0662684 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,14 @@ "@emailjs/browser": "^4.3.3", "@headlessui/react": "^1.7.18", "@heroicons/react": "^2.1.3", + "i18next": "^23.11.5", + "i18next-browser-languagedetector": "^8.0.0", + "i18next-http-backend": "^2.5.2", "localforage": "^1.10.0", "match-sorter": "^6.3.4", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^14.1.2", "react-icons": "^5.0.1", "react-router-dom": "^6.22.3", "react-toastify": "^10.0.5", diff --git a/public/locales/en.json b/public/locales/en.json new file mode 100644 index 0000000..b290042 --- /dev/null +++ b/public/locales/en.json @@ -0,0 +1,155 @@ +{ + "navbar": { + "services": "Services", + "advantages": "Advantages", + "contact": "Contact" + }, + + "hero": { + "heading": { + "part1": "GROW YOUR", + "part2": "BUSINESS" + }, + "subheading": { + "part1": "We do all the digital footprint for you,", + "part2": "so you can focus on what's important!" + }, + "button": "Yes, I want that!" + }, + + "services": { + "heading": { + "part1": "We've got your", + "part2": "footprint", + "part3": "bullsh", + "part4": "t covered!" + }, + "subheading": "Unlock the full potential of your business with our services.", + "cards": { + "marketing": { + "card1": { + "title": "Analyze", + "description": "Problem" + }, + "card2": { + "title": "Create", + "description": "Strategy" + }, + "card3": { + "title": "Scale", + "description": "Company" + }, + "card4": { + "title": "Execute", + "description": "Plan" + } + }, + "website": { + "card1": { + "title": "Idealize", + "description": "Website" + }, + "card2": { + "title": "Design", + "description": "Website" + }, + "card3": { + "title": "Develop", + "description": "Website" + }, + "card4": { + "title": "Build", + "description": "Website" + } + }, + "social": { + "card1": { + "title": "Analyze", + "description": "Algorithm" + }, + "card2": { + "title": "Schedule", + "description": "Content" + }, + "card3": { + "title": "Target", + "description": "Audience" + }, + "card4": { + "title": "Expand", + "description": "Platforms" + } + }, + "media": { + "card1": { + "title": "Envision", + "description": "Prespective" + }, + "card2": { + "title": "Capture", + "description": "Moments" + }, + "card3": { + "title": "Edit", + "description": "Moments" + }, + "card4": { + "title": "Publish", + "description": "Content" + } + } + } + }, + "results": { + "heading": { + "part1": "Ok... So what makes", + "part2": "us different?" + }, + "subheading": "We have helped many businesses grow and expand their digital footprint.", + "cards": { + "card1": { + "title": "GARANTEED", + "description": "We only win if you win!\nThat’s the basis for a good\npartnership.\nYou won’t carry all the risk,\nwe’ll share it." + }, + "card2": { + "title": "RESULTS", + "description": "Our first priority is to get\nyou results.\n\nLess talk, more walk." + }, + "card3": { + "title": "LOCAL", + "description": "We’re not a anonymous call\ncenter. We’re a local\ncompany, so you’ll be able to\nreach us when you need us" + }, + "card4": { + "title": "SPECIALIZED", + "description": "Jack of all trades... master\nof none Specialization\nworks. We work with\nindustries we know, so we\ncan guarantee results." + } + } + }, + "contact": { + "heading": { + "part1": "Contact Us For", + "part2": "A", + "part3": "Free", + "part4": "Analysis" + }, + "subheading": { + "part1": "Would you like to know what we could do for you? Fill out the form ad we’ll get", + "part2": "back to you No obligation, no annoying tactics. We won’t waste your time." + }, + "form": { + "name": "Name", + "email": "Email", + "company": "Company", + "phone": "Phone", + "question": "What is your most important question?", + "message": "Message", + "button": "Send" + }, + + "toasts": { + "success": "Your message has been sent successfully!", + "error": "An error occurred while sending your message. Please try again later.", + "empty": "Please fill out all the fields before sending the message." + } + } +} diff --git a/src/Components/Navbar/Navbar.tsx b/src/Components/Navbar/Navbar.tsx index 020b21b..6086bc8 100644 --- a/src/Components/Navbar/Navbar.tsx +++ b/src/Components/Navbar/Navbar.tsx @@ -2,9 +2,11 @@ import { useState } from "react"; import { FaTimes, FaBars } from "react-icons/fa"; import Logo from "../../assets/logo.svg"; +import { useTranslation } from "react-i18next"; export default () => { const [state, setState] = useState(false); + const { t } = useTranslation(); const [colorChange, setColorChange] = useState(false); const changeColor = () => { @@ -19,9 +21,9 @@ export default () => { // Replace javascript:void(0) path with your path const navigation = [ - { title: "Services", path: "#Services" }, - { title: "Results", path: "#Results" }, - { title: "Contact", path: "#Contact" }, + { title: t("navbar.services"), path: "Services" }, + { title: t("navbar.advantages"), path: "Advantages" }, + { title: t("navbar.contact"), path: "Contact" }, ]; return ( diff --git a/src/Components/Pages/Contact/ContactForm.tsx b/src/Components/Pages/Contact/ContactForm.tsx index c4007b3..71bd850 100644 --- a/src/Components/Pages/Contact/ContactForm.tsx +++ b/src/Components/Pages/Contact/ContactForm.tsx @@ -2,18 +2,21 @@ import React from "react"; import emailjs from "@emailjs/browser"; import { ToastContainer, toast } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; +import { useTranslation } from "react-i18next"; const ContactForm = () => { + const { t } = useTranslation(); + const notify = () => { - toast.success("Message Sent Successfully!"); + toast.success(t("contact.toasts.success")); }; const errorToast = () => { - toast.error("Error Sending Message!"); + toast.error(t("contact.toasts.error")); }; const emptyForm = () => { - toast.warning("Please fill all the fields!"); + toast.warning(t("contact.toasts.empty")); }; const sendEmail = (e: React.FormEvent) => { @@ -56,13 +59,13 @@ const ContactForm = () => { className="form-input" type="text" name="user_name" - placeholder="Name" + placeholder={t("contact.form.name")} />
@@ -70,13 +73,13 @@ const ContactForm = () => { className="form-input" type="text" name="user_company" - placeholder="Company" + placeholder={t("contact.form.company")} />
@@ -84,13 +87,13 @@ const ContactForm = () => { className="form-input" type="text" name="user_question" - placeholder="What is your most important question?" + placeholder={t("contact.form.question")} >
@@ -100,7 +103,7 @@ const ContactForm = () => { type="submit" className="flex-1 font-medium md:px-8 py-3 text-2xl md:text-3xl bg-gradient-to-br from-custom-bglight to-custom-bgdark rounded-xl hover:bg-transparent hover:from-transparent hover:to-transparent transition duration-500" > - Send + {t("contact.form.button")} diff --git a/src/Components/Pages/Contact/ContactPage.tsx b/src/Components/Pages/Contact/ContactPage.tsx index 6d04fc3..3cafca1 100644 --- a/src/Components/Pages/Contact/ContactPage.tsx +++ b/src/Components/Pages/Contact/ContactPage.tsx @@ -1,21 +1,21 @@ +import { useTranslation } from "react-i18next"; import ContactForm from "./ContactForm"; const ContactPage = () => { + const { t } = useTranslation(); + return (

- Contact Us For
A{" "} - - Free - {" "} - Analysis + {t("contact.heading.part1")}
+ {t("contact.heading.part2")}{" "} + {t("contact.heading.part3")}{" "} + {t("contact.heading.part4")}

- Would you like to know what we could do for you? Fill out the form ad - we’ll get
- back to you No obligation, no annoying tactics. We won’t waste your - time. + {t("contact.subheading.part1")}
+ {t("contact.subheading.part2")}

diff --git a/src/Components/Pages/Hero/Hero.tsx b/src/Components/Pages/Hero/Hero.tsx index f28324d..432530a 100644 --- a/src/Components/Pages/Hero/Hero.tsx +++ b/src/Components/Pages/Hero/Hero.tsx @@ -1,7 +1,10 @@ +import { useTranslation } from "react-i18next"; import HeroImg from "../../../assets/HeroImg.svg"; import Stars from "../../../assets/Stars.svg"; const Hero = () => { + const { t } = useTranslation(); + return (
@@ -15,8 +18,8 @@ const Hero = () => { className="md:px-0 ml-auto mr-auto lg:mr-0 lg:text-right" >
-

GROW YOUR

-

BUSINESS

+

{t("hero.heading.part1")}

+

{t("hero.heading.part2")}

{ >
-

- We do all the digital footprint for you, -

-

- so you can focus on what's important! -

-
+
+

+ {t("hero.subheading.part1")} +

+

+ {t("hero.subheading.part2")} +

+
+ diff --git a/src/Components/Pages/Results/CardContainer.tsx b/src/Components/Pages/Results/CardContainer.tsx index 779ff9f..55c83a3 100644 --- a/src/Components/Pages/Results/CardContainer.tsx +++ b/src/Components/Pages/Results/CardContainer.tsx @@ -1,23 +1,26 @@ +import { useTranslation } from "react-i18next"; import Card from "./Card"; const CardContainer = () => { + const { t } = useTranslation(); + return (
); diff --git a/src/Components/Pages/Results/ResultsPage.tsx b/src/Components/Pages/Results/ResultsPage.tsx index e8ba084..4118d4a 100644 --- a/src/Components/Pages/Results/ResultsPage.tsx +++ b/src/Components/Pages/Results/ResultsPage.tsx @@ -1,17 +1,15 @@ +import { useTranslation } from "react-i18next"; import CardContainer from "./CardContainer"; const ResultsPage = () => { + const { t } = useTranslation(); + return (

- - " - - Ok... So what makes
- us different? - - " - + " + {t("results.heading.part1")}
{t("results.heading.part2")} + "

diff --git a/src/Components/Pages/Services/CameraCards.tsx b/src/Components/Pages/Services/CameraCards.tsx index f616cd2..5b79b98 100644 --- a/src/Components/Pages/Services/CameraCards.tsx +++ b/src/Components/Pages/Services/CameraCards.tsx @@ -3,14 +3,33 @@ import EnvisionImg from "../../../assets/imgs/Envision.jpg"; import CaptureImg from "../../../assets/imgs/Capture.png"; import EditImg from "../../../assets/imgs/Edit.png"; import PublishImg from "../../../assets/imgs/Publish.jpg"; +import { useTranslation } from "react-i18next"; const CameraCards = () => { + const { t } = useTranslation(); + return (
- - - - + + + +
); }; diff --git a/src/Components/Pages/Services/MarketingCards.tsx b/src/Components/Pages/Services/MarketingCards.tsx index f72b3fb..389f237 100644 --- a/src/Components/Pages/Services/MarketingCards.tsx +++ b/src/Components/Pages/Services/MarketingCards.tsx @@ -3,14 +3,33 @@ import AnaliseImg from "../../../assets/imgs/Analise.jpg"; import CreateImg from "../../../assets/imgs/Create.jpg"; import ScaleImg from "../../../assets/imgs/Scale.jpg"; import ExecuteImg from "../../../assets/imgs/Execute.jpg"; +import { useTranslation } from "react-i18next"; const MarketingCards = () => { + const { t } = useTranslation(); + return (
- - - - + + + +
); }; diff --git a/src/Components/Pages/Services/Services.tsx b/src/Components/Pages/Services/Services.tsx index b715890..2b70d6e 100644 --- a/src/Components/Pages/Services/Services.tsx +++ b/src/Components/Pages/Services/Services.tsx @@ -1,13 +1,16 @@ +import { useTranslation } from "react-i18next"; import Underline from "../../../assets/Underline.svg"; import ServiceCardContainer from "./ServiceCardContainer"; const Services = () => { + const { t } = useTranslation(); + return (

- We've got your{" "} + {t("services.heading.part1")}{" "} - footprint{" "} + {t("services.heading.part2")}{" "} underline { {" "}

- bullsh - - * - - t covered + {t("services.heading.part3")} + * + {t("services.heading.part4")}

-

- Unlock the full potential of your business with our services. -

+

{t("services.subheading")}

diff --git a/src/Components/Pages/Services/SocialMedia.tsx b/src/Components/Pages/Services/SocialMedia.tsx index fd82bba..1c9edfb 100644 --- a/src/Components/Pages/Services/SocialMedia.tsx +++ b/src/Components/Pages/Services/SocialMedia.tsx @@ -3,14 +3,33 @@ import AlgorithmImg from "../../../assets/imgs/Algorithm.jpg"; import ScheduleImg from "../../../assets/imgs/Schedule.jpg"; import TargetImg from "../../../assets/imgs/Target.jpg"; import ExpandImg from "../../../assets/imgs/Expand.jpg"; +import { useTranslation } from "react-i18next"; const SocialMediaCards = () => { + const { t } = useTranslation(); + return (
- - - - + + + +
); }; diff --git a/src/Components/Pages/Services/WesiteCards.tsx b/src/Components/Pages/Services/WesiteCards.tsx index 3ffe6af..d6a29a3 100644 --- a/src/Components/Pages/Services/WesiteCards.tsx +++ b/src/Components/Pages/Services/WesiteCards.tsx @@ -3,14 +3,33 @@ import IdealizeImg from "../../../assets/imgs/Idealize.png"; import DesignImg from "../../../assets/imgs/Design.png"; import CodeImg from "../../../assets/imgs/Code.jpg"; import BuildImg from "../../../assets/imgs/Build.png"; +import { useTranslation } from "react-i18next"; const WesiteCards = () => { + const { t } = useTranslation(); + return (
- - - - + + + +
); }; diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 0000000..836b902 --- /dev/null +++ b/src/i18n.ts @@ -0,0 +1,27 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; +import LanguageDetector from "i18next-browser-languagedetector"; +import Backend from "i18next-http-backend"; + +i18n + .use(Backend) + // detect user language + // learn more: https://github.com/i18next/i18next-browser-languageDetector + .use(LanguageDetector) + // pass the i18n instance to react-i18next. + .use(initReactI18next) + // init i18next + // for all options read: https://www.i18next.com/overview/configuration-options + .init({ + backend: { + // translation file path + loadPath: "/locales/{{lng}}.json", + }, + debug: true, + fallbackLng: "en", + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + }, + }); + +export default i18n; diff --git a/src/main.tsx b/src/main.tsx index ba549fb..8fa5728 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,13 +1,17 @@ -import React from "react"; +import React, { Suspense } from "react"; import ReactDOM from "react-dom/client"; import App from "./App.tsx"; import "./index.css"; import { BrowserRouter } from "react-router-dom"; +import "./i18n"; + ReactDOM.createRoot(document.getElementById("root")!).render( - + + + ); diff --git a/src/routes/Home.tsx b/src/routes/Home.tsx index 2ad367a..628aba7 100644 --- a/src/routes/Home.tsx +++ b/src/routes/Home.tsx @@ -13,7 +13,7 @@ const Home = () => {
-
+