From 052e11c17bbeb2d70edb6613bc8982742931bde3 Mon Sep 17 00:00:00 2001 From: Ebube Anyanwu Date: Fri, 16 Aug 2024 10:23:57 +0100 Subject: [PATCH 1/2] feat/multiple language support for landing pages --- messages/en.json | 105 ++++++++++++++++++ messages/es.json | 105 ++++++++++++++++++ messages/fr.json | 105 ++++++++++++++++++ .../(auth-routes)/login/page.test-backup.tsx | 13 ++- src/app/(auth-routes)/login/page.tsx | 32 +++--- src/app/(auth-routes)/register/page.tsx | 45 ++++---- .../(home)/about-us/page.test.tsx | 4 +- .../(landing-routes)/(home)/pricing/page.tsx | 65 +++++++---- src/app/(landing-routes)/career/Nojobs.tsx | 7 +- src/app/(landing-routes)/career/page.test.tsx | 4 +- src/app/(landing-routes)/career/page.tsx | 30 ++++- .../help-center/page.test.tsx | 4 +- .../layout/navbar/organisation-switcher.tsx | 1 - src/components/layouts/aboutUs/CoreValues.tsx | 41 +++---- src/components/layouts/aboutUs/Hero.tsx | 29 ++++- src/components/layouts/aboutUs/Mission.tsx | 10 +- .../layouts/aboutUs/OurServices.tsx | 11 +- .../layouts/accordion/FaqsAccordion.tsx | 6 +- src/components/layouts/heading/index.tsx | 2 +- src/constants/faqsdata.ts | 15 +-- src/test/pricingpage/pricing.test.tsx | 7 +- 21 files changed, 507 insertions(+), 134 deletions(-) diff --git a/messages/en.json b/messages/en.json index 2a941f571..c1252113b 100644 --- a/messages/en.json +++ b/messages/en.json @@ -8,6 +8,46 @@ "pricing": "Pricing", "careers": "Careers" }, + "login": { + "title": "Login", + "welcomeBack": "Welcome back, you've been missed!", + "continueWithGoogle": "Continue with Google", + "emailPlaceholder": "Enter Email Address", + "passwordPlaceholder": "Enter Password", + "rememberMe": "Remember me", + "forgotPassword": "Forgot Password?", + "loginButton": "Login", + "signInWithMagicLink": "Sign in with magic link", + "noAccount": "Don't Have An Account?", + "agree": "By logging in, you agree to the", + "and": "and", + "signUp": "Sign Up", + "termsOfService": "Terms of Service", + "privacyPolicy": "Privacy Policy", + "loggingIn": "Logging in..." + }, + "register": { + "signUp": "Sign Up", + "createAccountDesc": "Create an account to get started with us.", + "continueWithGoogle": "Continue with Google", + "firstName": "First Name", + "firstNamePlaceholder": "Enter your first name", + "lastName": "Last Name", + "lastNamePlaceholder": "Enter your last name", + "email": "Email", + "emailPlaceholder": "Enter Email Address", + "password": "Password", + "passwordPlaceholder": "Enter Password", + "createAccount": "Create Account", + "loggingIn": "Logging in...", + "emailVerification": "Email Verification", + "emailVerificationDesc": "We have sent a code to your email {email}", + "checkSpam": "check your spam if you do not receive the email", + "otpExpiresIn": "OTP expires in: {timeLeft}", + "resendCode": "Didn't receive the code? resend", + "dataProcessing": "We would process your data as set forth in our Terms of Use, Privacy Policy and Data Processing Agreement", + "alreadyHaveAccount": "Already Have An Account? Login" + }, "HomePage": { "title": "Hello world!" }, @@ -80,5 +120,70 @@ "termsOfUse": "Terms of Use", "copyright": "2024 All Rights Reserved" } + }, + "pricing": { + "pricingTitle": "Simple and {{Affordable}} Pricing Plan", + "pricingContent": "Our flexible plans are designed to scale with your business. We have a plan for you.", + "monthly": "Monthly", + "annual": "Annual (save 20%)", + "billingPlansNotAvailable": "Billing plans not available", + "features": { + "feature1": "2 Projects", + "feature2": "Up to 100 subscribers", + "feature3": "Basic analytics", + "feature4": "24-hour support response time", + "feature5": "Marketing advisor", + "feature6": "Custom integration", + "continue": "Continue" + }, + "essentials": "The essentials to provide your best work for clients.", + "faqHeader": "Frequently Asked Questions", + "faqSubHeader": "We couldn’t answer your question?", + "contactUs": "Contact us", + "continue": "Continue" + }, + "faq": { + "question1": "What is the purpose of this application?", + "answer1": "This application is designed to help users manage their tasks efficiently by providing tools for scheduling, tracking progress, and setting reminders.", + "question2": "How do I reset my password?", + "answer2": "To reset your password, go to the login page and click on the 'Forgot Password' link. Follow the instructions to receive a password reset email and create a new password.", + "question3": "Can I use this application on multiple devices?", + "answer3": "Yes, the application is accessible from multiple devices including desktops, tablets, and smartphones. Your data will be synced across all devices, ensuring a seamless experience." + }, + "noJobs": { + "noJobsTitle": "No available Jobs at the moment", + "noJobsContent": "Come back later!" + }, + "ourServices": { + "title": "Our Services", + "description": "Trained to Give You The Best", + "details": "Since our founding in, Hng Boilerplate has been dedicated to constantly evolving to stay ahead of the curve. Our agile mindset and relentless pursuit of innovation ensure that you're always equipped with the most effective tools and strategies." + }, + "mission": { + "title": "Our Mission & Vision", + "subtitle": "Leading the Way, Redefining Industries", + "description": "At Hng Boilerplate, we are dedicated to exceeding your expectations. We strive to understand your unique needs and challenges, providing tailored solutions that drive real results and empower your success." + }, + "coreValues": { + "title": "Our Core Values", + "description": "Our Values shape the core of our organization and define the character of our industry.", + "values": { + "integrity": { + "title": "Integrity", + "description": "We uphold the highest ethical standards in everything we do, fostering trust and transparency with our clients, partners, and employees. We believe that honesty and integrity are the foundation of lasting success." + }, + "customerCentricity": { + "title": "Customer Centricity", + "description": "Our customers are at the heart of our business. We strive to understand their needs, exceed their expectations, and build lasting relationships based on trust and mutual respect. We believe that putting our customers first is the key to long-term success." + }, + "innovation": { + "title": "Innovation", + "description": "We embrace a culture of continuous improvement and creativity, constantly seeking new ways to evolve and enhance our products, services, and processes. We encourage experimentation and risk-taking, recognizing that innovation is essential for growth." + }, + "excellence": { + "title": "Excellence", + "description": "We are committed to delivering exceptional quality in everything we do, from our products and services to our customer interactions and internal processes. We strive for continuous improvement and hold ourselves to the highest standards of performance." + } + } } } \ No newline at end of file diff --git a/messages/es.json b/messages/es.json index 1d45fd409..2cedb5f4f 100644 --- a/messages/es.json +++ b/messages/es.json @@ -8,6 +8,46 @@ "pricing": "Precios", "careers": "Carreras" }, + "login": { + "title": "Iniciar sesión", + "welcomeBack": "¡Bienvenido de nuevo, te hemos extrañado!", + "continueWithGoogle": "Continuar con Google", + "emailPlaceholder": "Ingrese dirección de correo electrónico", + "passwordPlaceholder": "Ingrese contraseña", + "rememberMe": "Recuérdame", + "forgotPassword": "¿Olvidaste tu contraseña?", + "loginButton": "Iniciar sesión", + "signInWithMagicLink": "Iniciar sesión con enlace mágico", + "noAccount": "¿No tienes una cuenta?", + "agree": "Al iniciar sesión, aceptas los", + "and": "y", + "signUp": "Regístrate", + "termsOfService": "Términos del Servicio", + "privacyPolicy": "Política de Privacidad", + "loggingIn": "Iniciando sesión..." + }, + "register": { + "signUp": "Regístrate", + "createAccountDesc": "Crea una cuenta para comenzar con nosotros.", + "continueWithGoogle": "Continuar con Google", + "firstName": "Nombre", + "firstNamePlaceholder": "Ingresa tu nombre", + "lastName": "Apellido", + "lastNamePlaceholder": "Ingresa tu apellido", + "email": "Correo electrónico", + "emailPlaceholder": "Ingresa la dirección de correo", + "password": "Contraseña", + "passwordPlaceholder": "Ingresa la contraseña", + "createAccount": "Crear cuenta", + "loggingIn": "Iniciando sesión...", + "emailVerification": "Verificación de correo electrónico", + "emailVerificationDesc": "Hemos enviado un código a tu correo {email}", + "checkSpam": "verifica tu spam si no recibes el correo", + "otpExpiresIn": "OTP expira en: {timeLeft}", + "resendCode": "¿No recibiste el código? reenviar", + "dataProcessing": "Procesaremos tus datos según lo establecido en nuestros Términos de uso, Política de privacidad y Acuerdo de procesamiento de datos", + "alreadyHaveAccount": "¿Ya tienes una cuenta? Iniciar sesión" + }, "HomePage": { "title": "¡Hola, mundo!" }, @@ -82,5 +122,70 @@ "termsOfUse": "Términos de Uso", "copyright": "2024 Todos los Derechos Reservados" } + }, + "pricing": { + "pricingTitle": "Plan de Precios Simple y {{Asequible}}", + "pricingContent": "Nuestros planes flexibles están diseñados para adaptarse a su negocio. Tenemos un plan para usted.", + "monthly": "Mensual", + "annual": "Anual (ahorra un 20%)", + "billingPlansNotAvailable": "Planes de facturación no disponibles", + "features": { + "feature1": "2 Proyectos", + "feature2": "Hasta 100 suscriptores", + "feature3": "Análisis básico", + "feature4": "Tiempo de respuesta de soporte de 24 horas", + "feature5": "Asesor de marketing", + "feature6": "Integración personalizada", + "continue": "Continuar" + }, + "essentials": "Los elementos esenciales para ofrecer tu mejor trabajo a los clientes.", + "faqHeader": "Preguntas Frecuentes", + "faqSubHeader": "¿No pudimos responder a su pregunta?", + "contactUs": "Contáctanos", + "continue": "Continuar" + }, + "faq": { + "question1": "¿Cuál es el propósito de esta aplicación?", + "answer1": "Esta aplicación está diseñada para ayudar a los usuarios a gestionar sus tareas de manera eficiente proporcionando herramientas para la programación, el seguimiento del progreso y el establecimiento de recordatorios.", + "question2": "¿Cómo restablezco mi contraseña?", + "answer2": "Para restablecer tu contraseña, ve a la página de inicio de sesión y haz clic en el enlace 'Olvidé mi contraseña'. Sigue las instrucciones para recibir un correo electrónico de restablecimiento de contraseña y crear una nueva contraseña.", + "question3": "¿Puedo usar esta aplicación en varios dispositivos?", + "answer3": "Sí, la aplicación es accesible desde múltiples dispositivos, incluidos escritorios, tabletas y teléfonos inteligentes. Tus datos se sincronizarán en todos los dispositivos, garantizando una experiencia fluida." + }, + "noJobs": { + "noJobsTitle": "No hay trabajos disponibles en este momento", + "noJobsContent": "¡Vuelve más tarde!" + }, + "ourServices": { + "title": "Nuestros Servicios", + "description": "Capacitados para Ofrecerte Lo Mejor", + "details": "Desde nuestra fundación, Hng Boilerplate se ha dedicado a evolucionar constantemente para mantenerse a la vanguardia. Nuestra mentalidad ágil y nuestra búsqueda incesante de innovación garantizan que siempre estés equipado con las herramientas y estrategias más efectivas." + }, + "mission": { + "title": "Nuestra Misión y Visión", + "subtitle": "Liderando el Camino, Redefiniendo Industrias", + "description": "En Hng Boilerplate, estamos dedicados a superar tus expectativas. Nos esforzamos por entender tus necesidades y desafíos únicos, proporcionando soluciones personalizadas que generan resultados reales y potencian tu éxito." + }, + "coreValues": { + "title": "Nuestros Valores Fundamentales", + "description": "Nuestros Valores forman el núcleo de nuestra organización y definen el carácter de nuestra industria.", + "values": { + "integrity": { + "title": "Integridad", + "description": "Mantenemos los más altos estándares éticos en todo lo que hacemos, fomentando la confianza y la transparencia con nuestros clientes, socios y empleados. Creemos que la honestidad y la integridad son la base del éxito duradero." + }, + "customerCentricity": { + "title": "Enfoque en el Cliente", + "description": "Nuestros clientes están en el corazón de nuestro negocio. Nos esforzamos por entender sus necesidades, superar sus expectativas y construir relaciones duraderas basadas en la confianza y el respeto mutuo. Creemos que poner a nuestros clientes en primer lugar es la clave para el éxito a largo plazo." + }, + "innovation": { + "title": "Innovación", + "description": "Adoptamos una cultura de mejora continua y creatividad, buscando constantemente nuevas formas de evolucionar y mejorar nuestros productos, servicios y procesos. Fomentamos la experimentación y la toma de riesgos, reconociendo que la innovación es esencial para el crecimiento." + }, + "excellence": { + "title": "Excelencia", + "description": "Estamos comprometidos a ofrecer una calidad excepcional en todo lo que hacemos, desde nuestros productos y servicios hasta nuestras interacciones con los clientes y procesos internos. Buscamos la mejora continua y nos mantenemos a los más altos estándares de desempeño." + } + } } } \ No newline at end of file diff --git a/messages/fr.json b/messages/fr.json index 690a448bb..e258487f0 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -8,6 +8,46 @@ "pricing": "Tarification", "careers": "Carrières" }, + "login": { + "title": "Connexion", + "welcomeBack": "Bon retour, tu nous as manqué !", + "continueWithGoogle": "Continuer avec Google", + "emailPlaceholder": "Entrez l'adresse e-mail", + "passwordPlaceholder": "Entrez le mot de passe", + "rememberMe": "Se souvenir de moi", + "forgotPassword": "Mot de passe oublié ?", + "loginButton": "Connexion", + "signInWithMagicLink": "Se connecter avec un lien magique", + "noAccount": "Vous n'avez pas de compte ?", + "agree": "En vous connectant, vous acceptez les", + "and": "et", + "signUp": "S'inscrire", + "termsOfService": "Conditions d'utilisation", + "privacyPolicy": "Politique de confidentialité", + "loggingIn": "Connexion en cours..." + }, + "register": { + "signUp": "S'inscrire", + "createAccountDesc": "Créez un compte pour commencer avec nous.", + "continueWithGoogle": "Continuer avec Google", + "firstName": "Prénom", + "firstNamePlaceholder": "Entrez votre prénom", + "lastName": "Nom", + "lastNamePlaceholder": "Entrez votre nom", + "email": "Email", + "emailPlaceholder": "Entrez l'adresse email", + "password": "Mot de passe", + "passwordPlaceholder": "Entrez le mot de passe", + "createAccount": "Créer un compte", + "loggingIn": "Connexion en cours...", + "emailVerification": "Vérification de l'email", + "emailVerificationDesc": "Nous avons envoyé un code à votre email {email}", + "checkSpam": "vérifiez votre spam si vous ne recevez pas l'email", + "otpExpiresIn": "OTP expire dans : {timeLeft}", + "resendCode": "Vous n'avez pas reçu le code ? renvoyez", + "dataProcessing": "Nous traiterons vos données comme indiqué dans nos Conditions d'utilisation, Politique de confidentialité et Accord de traitement des données", + "alreadyHaveAccount": "Vous avez déjà un compte ? Connexion" + }, "HomePage": { "title": "Bonjour le monde !" }, @@ -82,5 +122,70 @@ "termsOfUse": "Conditions d'Utilisation", "copyright": "2024 Tous Droits Réservés" } + }, + "pricing": { + "pricingTitle": "Plan de Tarification Simple et {{Abordable}}", + "pricingContent": "Nos plans flexibles sont conçus pour évoluer avec votre entreprise. Nous avons un plan pour vous.", + "monthly": "Mensuel", + "annual": "Annuel (économisez 20%)", + "billingPlansNotAvailable": "Plans de facturation non disponibles", + "features": { + "feature1": "2 Projets", + "feature2": "Jusqu'à 100 abonnés", + "feature3": "Analyse de base", + "feature4": "Délai de réponse du support de 24 heures", + "feature5": "Conseiller marketing", + "feature6": "Intégration personnalisée", + "continue": "Continuer" + }, + "essentials": "Les éléments essentiels pour fournir votre meilleur travail aux clients.", + "faqHeader": "Questions Fréquemment Posées", + "faqSubHeader": "Nous n'avons pas pu répondre à votre question ?", + "contactUs": "Contactez-nous", + "continue": "Continuer" + }, + "faq": { + "question1": "Quel est le but de cette application ?", + "answer1": "Cette application est conçue pour aider les utilisateurs à gérer leurs tâches efficacement en fournissant des outils pour la planification, le suivi des progrès et la définition de rappels.", + "question2": "Comment réinitialiser mon mot de passe ?", + "answer2": "Pour réinitialiser votre mot de passe, allez sur la page de connexion et cliquez sur le lien 'Mot de passe oublié'. Suivez les instructions pour recevoir un e-mail de réinitialisation de mot de passe et créer un nouveau mot de passe.", + "question3": "Puis-je utiliser cette application sur plusieurs appareils ?", + "answer3": "Oui, l'application est accessible depuis plusieurs appareils, y compris les ordinateurs de bureau, les tablettes et les smartphones. Vos données seront synchronisées sur tous les appareils, garantissant une expérience fluide." + }, + "noJobs": { + "noJobsTitle": "Aucun emploi disponible pour le moment", + "noJobsContent": "Revenez plus tard !" + }, + "ourServices": { + "title": "Nos Services", + "description": "Formés pour Vous Offrir le Meilleur", + "details": "Depuis notre création, Hng Boilerplate est dédié à évoluer constamment pour rester en avance. Notre état d’esprit agile et notre quête incessante d'innovation garantissent que vous disposez toujours des outils et des stratégies les plus efficaces." + }, + "mission": { + "title": "Notre Mission & Vision", + "subtitle": "Ouvrir la Voie, Redéfinir les Industries", + "description": "Chez Hng Boilerplate, nous sommes dédiés à dépasser vos attentes. Nous nous efforçons de comprendre vos besoins et défis uniques, en fournissant des solutions sur mesure qui génèrent des résultats réels et favorisent votre succès." + }, + "coreValues": { + "title": "Nos Valeurs Fondamentales", + "description": "Nos Valeurs forment le cœur de notre organisation et définissent le caractère de notre secteur.", + "values": { + "integrity": { + "title": "Intégrité", + "description": "Nous maintenons les plus hauts standards éthiques dans tout ce que nous faisons, favorisant la confiance et la transparence avec nos clients, partenaires et employés. Nous croyons que l'honnêteté et l'intégrité sont les fondements d'un succès durable." + }, + "customerCentricity": { + "title": "Centricité Client", + "description": "Nos clients sont au cœur de notre activité. Nous nous efforçons de comprendre leurs besoins, de dépasser leurs attentes et de construire des relations durables basées sur la confiance et le respect mutuel. Nous croyons que placer nos clients en premier est la clé du succès à long terme." + }, + "innovation": { + "title": "Innovation", + "description": "Nous adoptons une culture d'amélioration continue et de créativité, cherchant constamment de nouvelles façons d'évoluer et d'améliorer nos produits, services et processus. Nous encourageons l'expérimentation et la prise de risques, reconnaissant que l'innovation est essentielle pour la croissance." + }, + "excellence": { + "title": "Excellence", + "description": "Nous nous engageons à offrir une qualité exceptionnelle dans tout ce que nous faisons, de nos produits et services à nos interactions avec les clients et nos processus internes. Nous visons l'amélioration continue et nous nous tenons aux plus hauts standards de performance." + } + } } } \ No newline at end of file diff --git a/src/app/(auth-routes)/login/page.test-backup.tsx b/src/app/(auth-routes)/login/page.test-backup.tsx index 45d6ea9ad..2fcfec8a9 100644 --- a/src/app/(auth-routes)/login/page.test-backup.tsx +++ b/src/app/(auth-routes)/login/page.test-backup.tsx @@ -1,7 +1,8 @@ -import { fireEvent, render, screen } from "@testing-library/react"; +import { fireEvent, screen } from "@testing-library/react"; import React from "react"; import { describe, expect, it, vi } from "vitest"; +import { renderWithIntl } from "~/test/utils"; import Login from "./page"; vi.mock("next/link", () => ({ @@ -91,7 +92,7 @@ describe("login", () => { it("renders login form", () => { expect.hasAssertions(); - render(); + renderWithIntl(); expect(screen.getByRole("heading", { name: "Login" })).toBeInTheDocument(); expect( @@ -104,7 +105,7 @@ describe("login", () => { it("toggles password visibility", () => { expect.hasAssertions(); - render(); + renderWithIntl(); const passwordInput = screen.getByPlaceholderText("Enter Password"); const toggleButton = screen.getByRole("button", { name: "" }); @@ -126,7 +127,7 @@ describe("login", () => { it('renders "Sign in with magic link" button', () => { expect.hasAssertions(); - render(); + renderWithIntl(); const magicLinkButton = screen.getByRole("button", { name: /sign in with magic link/i, @@ -138,7 +139,7 @@ describe("login", () => { it("renders Terms of Service and Privacy Policy links", () => { expect.hasAssertions(); - render(); + renderWithIntl(); const termsLink = screen.getByRole("link", { name: /terms of service/i }); expect(termsLink).toBeInTheDocument(); @@ -154,7 +155,7 @@ describe("login", () => { it("submits form with valid inputs", async () => { expect.hasAssertions(); - render(); + renderWithIntl(); const emailInput = screen.getByPlaceholderText("Enter Email Address"); const passwordInput = screen.getByPlaceholderText("Enter Password"); diff --git a/src/app/(auth-routes)/login/page.tsx b/src/app/(auth-routes)/login/page.tsx index 8b8a5ad2a..553cfd797 100644 --- a/src/app/(auth-routes)/login/page.tsx +++ b/src/app/(auth-routes)/login/page.tsx @@ -3,6 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { Eye, EyeOff, ShieldCheck } from "lucide-react"; import { signIn, useSession } from "next-auth/react"; +import { useTranslations } from "next-intl"; import { useRouter } from "next-nprogress-bar"; import Link from "next/link"; import { useEffect, useState, useTransition } from "react"; @@ -29,6 +30,7 @@ import { LoginSchema } from "~/schemas"; import { Organisation } from "~/types"; const Login = () => { + const t = useTranslations("login"); const router = useRouter(); const { toast } = useToast(); const { status } = useSession(); @@ -94,10 +96,10 @@ const Login = () => {

- Login + {t("title")}

- Welcome back, you've been missed! + {t("welcomeBack")}

@@ -132,7 +134,7 @@ const Login = () => { } > - Continue with Google + {t("continueWithGoogle")}
@@ -155,7 +157,7 @@ const Login = () => { { { />
- Remember me + {t("rememberMe")}
)} @@ -234,7 +236,7 @@ const Login = () => { href="/forgot-password" className="text-neutralColor-dark-2 text-sm font-medium" > - Forgot Password? + {t("forgotPassword")}
@@ -251,7 +253,7 @@ const Login = () => { ) : ( - Login + {t("loginButton")} )} @@ -263,35 +265,35 @@ const Login = () => { size="default" className="w-full py-6" > - Sign in with magic link + {t("signInWithMagicLink")}

- Don't Have An Account?{" "} + {t("noAccount")}{" "} - Sign Up + {t("signUp")}

- By logging in, you agree to the{" "} + {t("agree")}{" "} - Terms of Service + {t("termsOfService")} {" "} - and{" "} + {t("and")}{" "} - Privacy Policy + {t("privacyPolicy")}

diff --git a/src/app/(auth-routes)/register/page.tsx b/src/app/(auth-routes)/register/page.tsx index 56928a37a..a19c53db1 100644 --- a/src/app/(auth-routes)/register/page.tsx +++ b/src/app/(auth-routes)/register/page.tsx @@ -3,6 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { Eye, EyeOff } from "lucide-react"; import { signIn, useSession } from "next-auth/react"; +import { useTranslations } from "next-intl"; import { useRouter } from "next-nprogress-bar"; import Link from "next/link"; import { useState, useTransition } from "react"; @@ -39,6 +40,7 @@ import { RegisterSchema } from "~/schemas"; import { formatTime, maskEmail } from "~/utils"; const Register = () => { + const t = useTranslations("register"); const router = useRouter(); const { toast } = useToast(); const { status } = useSession(); @@ -125,10 +127,10 @@ const Register = () => {

- Sign Up + {t("signUp")}

- Create an account to get started with us. + {t("createAccountDesc")}

@@ -163,7 +165,7 @@ const Register = () => { } > - Continue with Google + {t("continueWithGoogle")}
@@ -181,13 +183,13 @@ const Register = () => { render={({ field }) => ( - First Name + {t("firstName")} { render={({ field }) => ( - Last Name + {t("lastName")} { render={({ field }) => ( - Email + {t("email")} { render={({ field }) => ( - Password + {t("password")}
{ > {isLoading ? ( - Logging in...{" "} + {t("loggingIn")}{" "} ) : ( - Create Account + {t("createAccount")} )} @@ -318,16 +320,18 @@ const Register = () => { > - Email Verification + {t("emailVerification")}

- We have sent a code to your email{" "} + {t("emailVerificationDesc")}{" "} {maskEmail(form.getValues().email)}

- check your spam if you do not recive the email - OTP expires in: {formatTime(timeLeft)} + {t("checkSpam")} + + {t("otpExpiresIn")}: {formatTime(timeLeft)} +
@@ -349,7 +353,7 @@ const Register = () => {

- Didn't receive the code?{" "} + {t("resendCode")}{" "} resendOtpreq()} @@ -359,14 +363,13 @@ const Register = () => {

- We would process your data as set forth in our Terms of Use, - Privacy Policy and Data Processing Agreement + {t("dataProcessing")}

- Already Have An Account?{" "} + {t("alreadyHaveAccount")}{" "} { it("should render correctly", () => { expect.assertions(1); - render(); + renderWithIntl(); expect(true).toBeTruthy(); }); diff --git a/src/app/(landing-routes)/(home)/pricing/page.tsx b/src/app/(landing-routes)/(home)/pricing/page.tsx index 8a0af4aac..cf2206e5a 100644 --- a/src/app/(landing-routes)/(home)/pricing/page.tsx +++ b/src/app/(landing-routes)/(home)/pricing/page.tsx @@ -1,6 +1,8 @@ "use client"; import axios from "axios"; +import { getCookie } from "cookies-next"; +import { useTranslations } from "next-intl"; import Image from "next/image"; import Link from "next/link"; import { useEffect, useState } from "react"; @@ -29,12 +31,18 @@ export default function Pricing() { const [plans, setPlans] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(); + const locale = getCookie("NEXT_LOCALE") || "en"; + const t = useTranslations("pricing"); useEffect(() => { const fetchPlans = async () => { try { const apiUrl = await getApiUrl(); - const response = await axios.get(`${apiUrl}/api/v1/billing-plans`); + const response = await axios.get(`${apiUrl}/api/v1/billing-plans`, { + headers: { + ...(locale ? { "Accept-Language": locale } : {}), + }, + }); setPlans(response.data.data); } catch { setError("Failed to fetch billing plans"); @@ -44,7 +52,7 @@ export default function Pricing() { }; fetchPlans(); - }, []); + }, [locale]); // @@ -55,14 +63,27 @@ export default function Pricing() { data-testid="pricing-container" >

- Monthly + {t("monthly")}
setToggle(2)} - className={`flex h-[50px] w-[190px] cursor-pointer items-center justify-center rounded-md ${toggle === 2 ? "bg-white font-medium" : ""}`} + className={`flex h-[50px] w-[215px] cursor-pointer items-center justify-center rounded-md ${toggle === 2 ? "bg-white font-medium" : ""}`} data-testid="annual-toggle" > - Annual(save 20%) + {t("annual")}
@@ -89,7 +110,7 @@ export default function Pricing() { {!loading && plans?.length === 0 && (
- Billing plans not available + {t("billingPlansNotAvailable")}
)} @@ -122,7 +143,7 @@ export default function Pricing() { className="mb-[46px] text-[14px]" data-testid={`${plan.name.toLowerCase()}-description`} > - The essentials to provide your best work for clients. + {t("essentials")}

- 2 Projects + {t("features.feature1")}
- Up to 100 subscribers + {t("features.feature2")}
- Basic analytics + {t("features.feature3")}
- 24-hour support response time + {t("features.feature4")}
- Marketing advisor + {t("features.feature5")}
- Custom integration + {t("features.feature6")}
))} @@ -223,11 +244,11 @@ export default function Pricing() { role="heading" aria-level={1} > - Frequently Asked Questions + {t("faqHeader")}

- We couldn’t answer your question? + {t("faqSubHeader")}

- Contact us + {t("contactUs")}
diff --git a/src/app/(landing-routes)/career/Nojobs.tsx b/src/app/(landing-routes)/career/Nojobs.tsx index 2775c210c..7408b1afc 100644 --- a/src/app/(landing-routes)/career/Nojobs.tsx +++ b/src/app/(landing-routes)/career/Nojobs.tsx @@ -1,4 +1,7 @@ +import { useTranslations } from "next-intl"; + function Nojobs() { + const t = useTranslations("noJobs"); return (
@@ -48,10 +51,10 @@ function Nojobs() {

- No available Jobs at the moment + {t("noJobsTitle")}

- Come back later! + {t("noJobsContent")}

); diff --git a/src/app/(landing-routes)/career/page.test.tsx b/src/app/(landing-routes)/career/page.test.tsx index 1c11e1cb9..35fd69869 100644 --- a/src/app/(landing-routes)/career/page.test.tsx +++ b/src/app/(landing-routes)/career/page.test.tsx @@ -1,4 +1,4 @@ -import { render } from "~/test/utils"; +import { renderWithIntl } from "~/test/utils"; import Page from "./page"; describe("page tests", () => { @@ -6,7 +6,7 @@ describe("page tests", () => { it.skip("should render correctly", () => { expect.assertions(1); - render(); + renderWithIntl(); expect(true).toBeTruthy(); }); diff --git a/src/app/(landing-routes)/career/page.tsx b/src/app/(landing-routes)/career/page.tsx index 9a7022b7b..08e127bff 100644 --- a/src/app/(landing-routes)/career/page.tsx +++ b/src/app/(landing-routes)/career/page.tsx @@ -1,6 +1,7 @@ "use client"; import axios from "axios"; +import { getCookie } from "cookies-next"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; @@ -21,12 +22,17 @@ export default function Career() { const pageSize = 6; const router = useRouter(); const { toast } = useToast(); + const locale = getCookie("NEXT_LOCALE") || "en"; useEffect(() => { const fetchCarrers = async () => { try { const apiUrl = await getApiUrl(); - const response = await axios.get(`${apiUrl}/api/v1/jobs`); + const response = await axios.get(`${apiUrl}/api/v1/jobs`, { + headers: { + ...(locale ? { "Accept-Language": locale } : {}), + }, + }); setAllJobs(response.data.data); } catch { toast({ @@ -40,7 +46,7 @@ export default function Career() { }; fetchCarrers(); - }, [toast]); + }, [locale, toast]); useEffect(() => { // Calculate the jobs to display based on the current page @@ -67,9 +73,23 @@ export default function Career() { return (
diff --git a/src/app/(landing-routes)/help-center/page.test.tsx b/src/app/(landing-routes)/help-center/page.test.tsx index dea52ad56..e182d33cc 100644 --- a/src/app/(landing-routes)/help-center/page.test.tsx +++ b/src/app/(landing-routes)/help-center/page.test.tsx @@ -1,11 +1,11 @@ -import { render } from "~/test/utils"; +import { render, renderWithIntl } from "~/test/utils"; import Page from "./page"; describe("page tests", () => { it("should render correctly", () => { expect.assertions(1); - render(); + renderWithIntl(); expect(true).toBeTruthy(); }); diff --git a/src/app/dashboard/(user-dashboard)/_components/layout/navbar/organisation-switcher.tsx b/src/app/dashboard/(user-dashboard)/_components/layout/navbar/organisation-switcher.tsx index a047888c1..36b1f6e0a 100644 --- a/src/app/dashboard/(user-dashboard)/_components/layout/navbar/organisation-switcher.tsx +++ b/src/app/dashboard/(user-dashboard)/_components/layout/navbar/organisation-switcher.tsx @@ -24,7 +24,6 @@ export const OrganisationSwitcher = () => { ); const { organizations, isLoading, switchOrganization } = useOrgContext(); - // eslint-disable-next-line unicorn/consistent-function-scoping useEffect(() => { if (!currentOrgId && organizations.length > 0) { diff --git a/src/components/layouts/aboutUs/CoreValues.tsx b/src/components/layouts/aboutUs/CoreValues.tsx index 183727015..1d3145f64 100644 --- a/src/components/layouts/aboutUs/CoreValues.tsx +++ b/src/components/layouts/aboutUs/CoreValues.tsx @@ -1,15 +1,17 @@ +import { useTranslations } from "next-intl"; + const CoreValues = () => { + const t = useTranslations("coreValues"); return (
-

- Our Core Values +

+ {t("title")}

- Our Value shapes the core of our organization, and defines the - character of our industry + {t("description")}

@@ -19,26 +21,19 @@ const CoreValues = () => { className="mb-3 text-[18px] font-[700] text-primary sm:text-[20px]" data-testid="integrity" > - Integrity + {t("values.integrity.title")}

- We uphold the highest ethical standards in everything we do, - fostering trust and transparency with our clients, partners, and - employees. We believe that honesty and integrity are the - foundation of lasting success. + {t("values.integrity.description")}

- Customer Centricity + {t("values.customerCentricity.title")}

- Our customers are at the heart of our business. We strive to - understand their needs, exceed their expectations, and build - lasting relationships based on trust and mutual respect. We - believe that putting our customers first is the key to long-term - success. + {t("values.customerCentricity.description")}

@@ -47,27 +42,19 @@ const CoreValues = () => { className="mb-3 text-[18px] font-[700] text-primary sm:text-[20px]" data-testid="innovation" > - Innovation + {t("values.innovation.title")}

- We embrace a culture of continuous improvement and creativity, - constantly seeking new ways to evolve and enhance our products, - services, and processes. We encourage experimentation and - risk-taking, recognizing that innovation is essential for - growth. + {t("values.innovation.description")}

- Excellence + {t("values.excellence.title")}

- We are committed to delivering exceptional quality in everything - we do, from our products and services to our customer - interactions and internal processes. We strive for continuous - improvement and hold ourselves to the highest standards of - performance. + {t("values.excellence.description")}

diff --git a/src/components/layouts/aboutUs/Hero.tsx b/src/components/layouts/aboutUs/Hero.tsx index 84ba9a8c8..ffc364218 100644 --- a/src/components/layouts/aboutUs/Hero.tsx +++ b/src/components/layouts/aboutUs/Hero.tsx @@ -1,15 +1,38 @@ +"use client"; + +import { getCookie } from "cookies-next"; import Image from "next/image"; import Heading from "../heading"; const Hero = () => { + const locale = getCookie("NEXT_LOCALE") || "en"; + return (
diff --git a/src/components/layouts/aboutUs/Mission.tsx b/src/components/layouts/aboutUs/Mission.tsx index 0faeac106..e8275a997 100644 --- a/src/components/layouts/aboutUs/Mission.tsx +++ b/src/components/layouts/aboutUs/Mission.tsx @@ -1,6 +1,8 @@ +import { useTranslations } from "next-intl"; import Image from "next/image"; const Mission = () => { + const t = useTranslations("mission"); return (
@@ -19,18 +21,16 @@ const Mission = () => { className="mb-3 text-lg font-[700] text-primary sm:text-xl" data-testid="mission web" > - Our Mission & Vision + {t("title")}

- Leading the Way, Redefining Industries + {t("subtitle")}

- At Hng Boilerplate, we are dedicated to exceeding your expectations. - We strive to understand your unique needs and challenges, providing - tailored solutions that drive real results and empower your success. + {t("description")}

diff --git a/src/components/layouts/aboutUs/OurServices.tsx b/src/components/layouts/aboutUs/OurServices.tsx index 9b8767c05..b6670e6dd 100644 --- a/src/components/layouts/aboutUs/OurServices.tsx +++ b/src/components/layouts/aboutUs/OurServices.tsx @@ -1,18 +1,20 @@ "use client"; +import { useTranslations } from "next-intl"; import Image from "next/image"; const OurServices = () => { + const t = useTranslations("ourServices"); return (

- Our Services + {t("title")}

- Trained to Give You The Best + {t("description")} {

- {`Since our founding in, Hng Boilerplate has been dedicated to - constantly evolving to stay ahead of the curve. Our agile mindset - and relentless pursuit of innovation ensure that you're always - equipped with the most effective tools and strategies.`} + {t("details")}

diff --git a/src/components/layouts/accordion/FaqsAccordion.tsx b/src/components/layouts/accordion/FaqsAccordion.tsx index 35841a8ac..22c994db0 100644 --- a/src/components/layouts/accordion/FaqsAccordion.tsx +++ b/src/components/layouts/accordion/FaqsAccordion.tsx @@ -1,4 +1,5 @@ import clsx from "clsx"; +import { useTranslations } from "next-intl"; import { Accordion, @@ -20,6 +21,7 @@ const FaqAccordion = ({ contentClassName, containerClassName, }: FaqAccordionProperties) => { + const t = useTranslations("faq"); return (
- {faq.question} + {t(`${faq.question}`)} - {faq.content} + {t(`${faq.content}`)} ))} diff --git a/src/components/layouts/heading/index.tsx b/src/components/layouts/heading/index.tsx index 487a61646..a10e4d821 100644 --- a/src/components/layouts/heading/index.tsx +++ b/src/components/layouts/heading/index.tsx @@ -37,7 +37,7 @@ const Heading = (properties: Properties) => { {renderTitle(properties.title)}

{properties?.content} diff --git a/src/constants/faqsdata.ts b/src/constants/faqsdata.ts index 3ef833759..420020512 100644 --- a/src/constants/faqsdata.ts +++ b/src/constants/faqsdata.ts @@ -1,17 +1,14 @@ export const faqData = [ { - question: "What is the purpose of this application?", - content: - "This application is designed to help users manage their tasks efficiently by providing tools for scheduling, tracking progress, and setting reminders.", + question: "question1", + content: "answer1", }, { - question: "How do I reset my password?", - content: - "To reset your password, go to the login page and click on the 'Forgot Password' link. Follow the instructions to receive a password reset email and create a new password.", + question: "question2", + content: "answer2", }, { - question: "Can I use this application on multiple devices?", - content: - "Yes, the application is accessible from multiple devices including desktops, tablets, and smartphones. Your data will be synced across all devices, ensuring a seamless experience.", + question: "question3", + content: "answer3", }, ]; diff --git a/src/test/pricingpage/pricing.test.tsx b/src/test/pricingpage/pricing.test.tsx index 4cb35f5a5..06fce67f3 100644 --- a/src/test/pricingpage/pricing.test.tsx +++ b/src/test/pricingpage/pricing.test.tsx @@ -1,22 +1,23 @@ -import { render, screen } from "@testing-library/react"; +import { screen } from "@testing-library/react"; import "@testing-library/jest-dom"; import Pricing from "~/app/(landing-routes)/(home)/pricing/page"; +import { renderWithIntl } from "../utils"; // describe("pricing Component", () => { it("renders the pricing header", () => { expect.assertions(1); - render(); + renderWithIntl(); const pricingHeader = screen.getByTestId("pricing-header"); expect(pricingHeader).toBeInTheDocument(); }); it("renders FAQ section", () => { expect.assertions(1); - render(); + renderWithIntl(); const faqSection = screen.getByTestId("faq-section"); expect(faqSection).toBeInTheDocument(); }); From 84afbda2dd17adffbddfc2dc6a860599fe6531ad Mon Sep 17 00:00:00 2001 From: Ebube Anyanwu Date: Fri, 16 Aug 2024 10:30:51 +0100 Subject: [PATCH 2/2] feat/multiple language support - fixes lint errors --- src/app/(landing-routes)/help-center/page.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(landing-routes)/help-center/page.test.tsx b/src/app/(landing-routes)/help-center/page.test.tsx index e182d33cc..39da3cfb1 100644 --- a/src/app/(landing-routes)/help-center/page.test.tsx +++ b/src/app/(landing-routes)/help-center/page.test.tsx @@ -1,4 +1,4 @@ -import { render, renderWithIntl } from "~/test/utils"; +import { renderWithIntl } from "~/test/utils"; import Page from "./page"; describe("page tests", () => {