feat: automatically detect language on server and translate UI (#530)

* feat: detect language on the server and translate page

* translate auth pages

* PR reviews
This commit is contained in:
Rotimi Best
2024-12-28 23:11:55 +01:00
committed by GitHub
parent 1f1ee8afd3
commit 381776f388
24 changed files with 431 additions and 232 deletions
@@ -1,34 +1,34 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import get from 'lodash/get';
import LogoFacebook from 'carbon-icons-svelte/lib/LogoFacebook.svelte';
import LogoTwitter from 'carbon-icons-svelte/lib/LogoTwitter.svelte';
import LogoLinkedin from 'carbon-icons-svelte/lib/LogoLinkedin.svelte';
import { Accordion, AccordionItem, Grid, Row, Column } from 'carbon-components-svelte';
import LocationFilled from 'carbon-icons-svelte/lib/LocationFilled.svelte';
import Phone from 'carbon-icons-svelte/lib/Phone.svelte';
import Email from 'carbon-icons-svelte/lib/Email.svelte';
import Rocket from 'carbon-icons-svelte/lib/Rocket.svelte';
import PageLoader from './PageLoader.svelte';
import { orgLandingpageValidation } from '$lib/utils/functions/validator';
import TextField from '$lib/components/Form/TextField.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import Navigation from '$lib/components/Navigation/index.svelte';
import { goto } from '$app/navigation';
import Box from '$lib/components/Box/index.svelte';
import Card from '$lib/components/Courses/components/Card/index.svelte';
import CardLoader from '$lib/components/Courses/components/Card/Loader.svelte';
import CoursesEmptyIcon from '$lib/components/Icons/CoursesEmptyIcon.svelte';
import { getSupabase } from '$lib/utils/functions/supabase';
import { courses, courseMetaDeta } from '$lib/components/Courses/store';
import { getCourseBySiteName } from '$lib/utils/services/org';
import { validateEmail } from '$lib/utils/functions/validateEmail';
import { courseMetaDeta, courses } from '$lib/components/Courses/store';
import TextArea from '$lib/components/Form/TextArea.svelte';
import { goto } from '$app/navigation';
import TextField from '$lib/components/Form/TextField.svelte';
import CoursesEmptyIcon from '$lib/components/Icons/CoursesEmptyIcon.svelte';
import Navigation from '$lib/components/Navigation/index.svelte';
import { landingPageSettings } from '$lib/components/Org/Settings/store';
import { t } from '$lib/utils/functions/translations';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import PoweredBy from '$lib/components/Upgrade/PoweredBy.svelte';
import { getSupabase } from '$lib/utils/functions/supabase';
import { t } from '$lib/utils/functions/translations';
import { validateEmail } from '$lib/utils/functions/validateEmail';
import { orgLandingpageValidation } from '$lib/utils/functions/validator';
import { getCourseBySiteName } from '$lib/utils/services/org';
import type { CurrentOrg } from '$lib/utils/types/org';
import { Accordion, AccordionItem, Column, Grid, Row } from 'carbon-components-svelte';
import Email from 'carbon-icons-svelte/lib/Email.svelte';
import LocationFilled from 'carbon-icons-svelte/lib/LocationFilled.svelte';
import LogoFacebook from 'carbon-icons-svelte/lib/LogoFacebook.svelte';
import LogoLinkedin from 'carbon-icons-svelte/lib/LogoLinkedin.svelte';
import LogoTwitter from 'carbon-icons-svelte/lib/LogoTwitter.svelte';
import Phone from 'carbon-icons-svelte/lib/Phone.svelte';
import Rocket from 'carbon-icons-svelte/lib/Rocket.svelte';
import get from 'lodash/get';
import { fade } from 'svelte/transition';
import PageLoader from './PageLoader.svelte';
export let orgSiteName = '';
export let org: CurrentOrg | null;
@@ -115,7 +115,6 @@
if (!siteName) return;
try {
console.log('sitename', siteName);
$courseMetaDeta.isLoading = true;
const coursesResult = await getCourseBySiteName(siteName);
courses.set(coursesResult);
@@ -182,7 +181,7 @@
{#if $landingPageSettings.header.show}
<header
id="header"
class={`w-full h-[100vh] md:h-[90vh] mb-10 relative ${
class={`relative mb-10 h-[100vh] w-full md:h-[90vh] ${
$landingPageSettings.header.background?.show
? 'bg-cover bg-center'
: 'border-b border-gray-300'
@@ -196,25 +195,25 @@
isOrgSite={true}
/>
<div class="absolute h-[100vh] md:h-[90vh] top-0 w-full opacity-80 z-10 bg-white" />
<div class="absolute top-0 z-10 h-[100vh] w-full bg-white opacity-80 md:h-[90vh]" />
{#if $landingPageSettings.header.banner.show}
<div class="flex items-center justify-center md:h-full py-2">
<div class="flex items-center justify-center py-2 md:h-full">
<div
class="md:w-11/12 lg:w-5/6 w-full flex items-center justify-center md:justify-between flex-col-reverse md:flex-row z-20 relative"
class="relative z-20 flex w-full flex-col-reverse items-center justify-center md:w-11/12 md:flex-row md:justify-between lg:w-5/6"
>
<!-- Course Description -->
<div class="md:w-2/5 w-11/12 py-10 flex flex-col items-center md:items-start mr-3">
<div class="mr-3 flex w-11/12 flex-col items-center py-10 md:w-2/5 md:items-start">
<p class=" text-primary-600 text-2xl font-semibold capitalize">
{org.name}
</p>
<h1
class="text-4xl md:text-5xl lg:text-6xl text-center md:text-start my-4 font-bold"
class="my-4 text-center text-4xl font-bold md:text-start md:text-5xl lg:text-6xl"
>
{$landingPageSettings.header.title} <br /><span class="text-primary-600"
>{$landingPageSettings.header.titleHighlight}</span
>
</h1>
<p class="text-md mb-3 md:mb-5 text-xl text-center md:text-start">
<p class="text-md mb-3 text-center text-xl md:mb-5 md:text-start">
{$landingPageSettings.header.subtitle}
</p>
@@ -228,7 +227,7 @@
/>
</div>
<div class="w-5/6 md:w-1/2 md:max-w-[650px] flex">
<div class="flex w-5/6 md:w-1/2 md:max-w-[650px]">
{#if isYouTubeLink($landingPageSettings.header?.banner?.video) && $landingPageSettings.header.banner.type === 'video'}
<div bind:this={player} id="player" style="width:100%; border-radius:12px">
<iframe
@@ -256,10 +255,10 @@
</div>
</div>
{:else}
<div class="w-full h-full flex items-center justify-center md:flex-row z-20 relative">
<div class="max-w-[600px] mx-auto w-11/12 py-10 flex flex-col items-center">
<div class="relative z-20 flex h-full w-full items-center justify-center md:flex-row">
<div class="mx-auto flex w-11/12 max-w-[600px] flex-col items-center py-10">
<p class=" text-primary-600 text-2xl font-semibold capitalize">{org.name}</p>
<h1 class="text-4xl md:text-5xl lg:text-6xl text-center my-4 font-bold">
<h1 class="my-4 text-center text-4xl font-bold md:text-5xl lg:text-6xl">
{$landingPageSettings.header.title} <br /><span class="text-primary-600"
>{$landingPageSettings.header.titleHighlight}</span
>
@@ -284,9 +283,9 @@
<!-- Our Story section -->
{#if $landingPageSettings.aboutUs.show}
<section id="aboutus" class="m-h-[400px] my-10 mx-auto max-w-6xl w-full">
<div class="mx-4 flex items-center justify-evenly flex-col lg:flex-row">
<div class="max-w-[600px] lg:w-2/5 mr-5 mb-5 lg:mb-0">
<section id="aboutus" class="m-h-[400px] mx-auto my-10 w-full max-w-6xl">
<div class="mx-4 flex flex-col items-center justify-evenly lg:flex-row">
<div class="mb-5 mr-5 max-w-[600px] lg:mb-0 lg:w-2/5">
<h2 class="text-4xl md:text-5xl lg:text-6xl">{$landingPageSettings.aboutUs.title}</h2>
<p class="mb-2">
{$landingPageSettings.aboutUs.content}
@@ -297,7 +296,7 @@
<img
src={$landingPageSettings.aboutUs.imageUrl}
alt="Our Story"
class=" rounded-2xl max-h-[450px]"
class=" max-h-[450px] rounded-2xl"
/>
</div>
</div>
@@ -306,10 +305,10 @@
<!-- Courses Section -->
{#if $landingPageSettings.courses.show}
<section id="courses" transition:fade class="my-10 mx-auto max-w-6xl w-full">
<section id="courses" transition:fade class="mx-auto my-10 w-full max-w-6xl">
<div class="w-full">
<div class="max-w-[500px] mx-auto w-11/12 py-10">
<h1 class="text-4xl md:text-5xl lg:text-6xl text-center my-4 font-bold">
<div class="mx-auto w-11/12 max-w-[500px] py-10">
<h1 class="my-4 text-center text-4xl font-bold md:text-5xl lg:text-6xl">
{$landingPageSettings.courses.title}
<span class="text-primary-600">{$landingPageSettings.courses.titleHighlight}</span>
</h1>
@@ -319,13 +318,13 @@
</div>
</div>
{#if $courseMetaDeta.isLoading}
<div class="cards-container my-4 mx-2">
<div class="cards-container mx-2 my-4">
<CardLoader />
<CardLoader />
<CardLoader />
</div>
{:else if $courses.length > 0}
<div class="cards-container my-4 mx-2">
<div class="cards-container mx-2 my-4">
{#each $courses.slice(0, viewAll ? $courses.length : 3) as courseData}
<Card
id={courseData.id}
@@ -350,17 +349,17 @@
{:else}
<Box>
<CoursesEmptyIcon />
<h3 class="dark:text-white text-2xl my-5">
<h3 class="my-5 text-2xl dark:text-white">
{$t('course.navItem.landing_page.no_course_published')}
</h3>
<p class="dark:text-white w-1/3 text-center">
<p class="w-1/3 text-center dark:text-white">
{$t('course.navItem.landing_page.coming_your_way')}
</p>
</Box>
{/if}
{#if $courses.length > 3}
<div class="w-full mt-3 flex justify-center">
<div class="mt-3 flex w-full justify-center">
<PrimaryButton
variant={VARIANTS.OUTLINED}
onClick={() => (viewAll = !viewAll)}
@@ -376,9 +375,9 @@
<!-- FAQ Section -->
{#if $landingPageSettings.faq.show}
<section id="faq" transition:fade class="my-10 mx-auto max-w-[700px] w-full">
<section id="faq" transition:fade class="mx-auto my-10 w-full max-w-[700px]">
<div class="py-10">
<h1 class="text-4xl md:text-5xl lg:text-6xl text-center my-4 font-bold">
<h1 class="my-4 text-center text-4xl font-bold md:text-5xl lg:text-6xl">
{$landingPageSettings.faq.title}
</h1>
</div>
@@ -398,10 +397,10 @@
<!-- Contact Section-->
{#if $landingPageSettings.contact.show}
<section id="contact" transition:fade class="my-10 w-full bg-primary-50">
<div class="mx-auto max-w-6xl w-full">
<div class="max-w-[500px] mx-auto w-11/12 py-10">
<h1 class="text-4xl md:text-5xl lg:text-6xl text-center my-4 font-bold">
<section id="contact" transition:fade class="bg-primary-50 my-10 w-full">
<div class="mx-auto w-full max-w-6xl">
<div class="mx-auto w-11/12 max-w-[500px] py-10">
<h1 class="my-4 text-center text-4xl font-bold md:text-5xl lg:text-6xl">
{$landingPageSettings.contact.title}
<span class="text-primary-600">{$landingPageSettings.contact.titleHighlight}</span>
</h1>
@@ -411,37 +410,37 @@
<!-- Contact Details -->
<Row>
<Column
class="flex items-center flex-col justify-center break-all text-center cursor-pointer hover:shadow-xl rounded-lg transition-all duration-500 py-2 mx-2"
class="mx-2 flex cursor-pointer flex-col items-center justify-center break-all rounded-lg py-2 text-center transition-all duration-500 hover:shadow-xl"
>
<LocationFilled size={32} />
<p class="text-xs md:text-sm mt-3 max-w-[200px]">
<p class="mt-3 max-w-[200px] text-xs md:text-sm">
{$landingPageSettings.contact.address}
</p>
</Column>
<Column
class="flex items-center flex-col justify-center break-all text-center cursor-pointer hover:shadow-xl rounded-lg transition-all duration-500 py-2 mx-2"
class="mx-2 flex cursor-pointer flex-col items-center justify-center break-all rounded-lg py-2 text-center transition-all duration-500 hover:shadow-xl"
>
<Phone size={32} />
<p class="text-xs md:text-sm mt-3">{$landingPageSettings.contact.phone}</p>
<p class="mt-3 text-xs md:text-sm">{$landingPageSettings.contact.phone}</p>
</Column>
<Column
class="flex items-center flex-col justify-center break-all text-center cursor-pointer hover:shadow-xl rounded-lg transition-all duration-500 py-2 mx-2"
class="mx-2 flex cursor-pointer flex-col items-center justify-center break-all rounded-lg py-2 text-center transition-all duration-500 hover:shadow-xl"
>
<Email size={32} />
<p class="text-xs md:text-sm mt-3">{$landingPageSettings.contact.email}</p>
<p class="mt-3 text-xs md:text-sm">{$landingPageSettings.contact.email}</p>
</Column>
</Row>
<!-- Contact Form -->
<div class="mt-8 bg-white p-7 rounded-lg">
<div class="mt-8 rounded-lg bg-white p-7">
{#if successContactSaved}
<div class="w-full flex items-center justify-center">
<div class="flex w-full items-center justify-center">
{$t('course.navItem.landing_page.thank_you')}
</div>
{:else}
<form on:submit|preventDefault={handleContactSubmit}>
<div class="w-full flex justify-between flex-col md:flex-row">
<div class="w-full md:w-2/4 mr-5">
<div class="flex w-full flex-col justify-between md:flex-row">
<div class="mr-5 w-full md:w-2/4">
<TextField
label={$t('course.navItem.landing_page.name')}
bind:value={contact.name}
@@ -481,7 +480,7 @@
type="submit"
isLoading={isContactSubmiting}
>
<span class="mr-2 text-md">{$t('course.navItem.landing_page.submit')}</span>
<span class="text-md mr-2">{$t('course.navItem.landing_page.submit')}</span>
<Rocket size={24} />
</PrimaryButton>
</form>
@@ -494,12 +493,12 @@
<!-- Waitlist Section -->
{#if $landingPageSettings.mailinglist.show}
<section id="waitlist" transition:fade class="my-10 mx-auto max-w-6xl w-[95%]">
<section id="waitlist" transition:fade class="mx-auto my-10 w-[95%] max-w-6xl">
<div
class="bg-primary-700 rounded-lg flex flex-col lg:flex-row lg:items-center px-4 md:px-10 py-14"
class="bg-primary-700 flex flex-col rounded-lg px-4 py-14 md:px-10 lg:flex-row lg:items-center"
>
<div class="w-full md:w-[65%] md:mr-4">
<h1 class="text-4xl font-bold mb-5 mt-0 text-white">
<div class="w-full md:mr-4 md:w-[65%]">
<h1 class="mb-5 mt-0 text-4xl font-bold text-white">
{$landingPageSettings.mailinglist.title}
</h1>
<p class="text-lg text-white">
@@ -507,7 +506,7 @@
</p>
</div>
<form on:submit|preventDefault={handleSubmit} class="my-4 w-full md:w-fit">
<div class="flex items-center flex-col sm:flex-row">
<div class="flex flex-col items-center sm:flex-row">
{#if success}
<p class="text-white">{$t('course.navItem.landing_page.successful_sub')}</p>
{:else}
@@ -538,9 +537,9 @@
{#if $landingPageSettings.footer.show}
<footer
id="footer"
class="flex justify-center items-center flex-col my-10 w-full px-5 py-10 md:py-3 border-b-0 border-r-0 border-t border-l-0 border-gray-300"
class="my-10 flex w-full flex-col items-center justify-center border-b-0 border-l-0 border-r-0 border-t border-gray-300 px-5 py-10 md:py-3"
>
<ul class="flex w-11/12 items-center flex-col sm:flex-row">
<ul class="flex w-11/12 flex-col items-center sm:flex-row">
<div class="logo">
<a
href="/"
@@ -552,16 +551,16 @@
<img
src={org.avatar_url || '/logo-192.png'}
alt={`${org.name} logo`}
class="rounded max-h-10 w-10 inline-block mx-auto"
class="mx-auto inline-block max-h-10 w-10 rounded"
data-atf="1"
/>
<h3 class="text-black ml-3 text-xl">{org.name}</h3>
<h3 class="ml-3 text-xl text-black">{org.name}</h3>
</a>
</div>
<span class="flex-grow" />
<div class="flex mt-5 sm:mt-0 gap-2">
<div class="mt-5 flex gap-2 sm:mt-0">
{#if $landingPageSettings.footer.facebook}
<li>
<a
@@ -2,13 +2,13 @@
import Modal from '$lib/components/Modal/index.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { snackbar } from '$lib/components/Snackbar/store';
import { profile } from '$lib/utils/store/user';
import { t } from '$lib/utils/functions/translations';
import {
triggerSendEmail,
NOTIFICATION_NAME
NOTIFICATION_NAME,
triggerSendEmail
} from '$lib/utils/services/notification/notification';
import { currentOrg } from '$lib/utils/store/org';
import { t } from '$lib/utils/functions/translations';
import { profile } from '$lib/utils/store/user';
const WAIT_SEC = 120;
const WAIT_TIME = WAIT_SEC * 1000;
@@ -51,21 +51,21 @@
}, WAIT_TIME);
};
$: open = Boolean(!$profile.is_email_verified && !!$currentOrg.id);
$: open = Boolean(!$profile.is_email_verified && !!$profile.id && !!$currentOrg.id);
$: open && sendVerificationCode();
</script>
<Modal {open} isCloseable={false} width="w-4/5" maxWidth="w-[500px]" containerClass="p-4">
<div class="flex flex-col items-center space-y-6 text-center">
<img src="/verify-email.svg" alt="email verification" />
<p class="font-bold text-xl">{$t('verify_email_modal.heading')}</p>
<p class="text-sm w-[70%] text-gray-700 dark:text-gray-200">
<p class="text-xl font-bold">{$t('verify_email_modal.heading')}</p>
<p class="w-[70%] text-sm text-gray-700 dark:text-gray-200">
{$t('verify_email_modal.sent_verification')}
{$profile.email}
{$t('verify_email_modal.to_confirm')}
</p>
<div class="flex items-center flex-col">
<div class="flex flex-col items-center">
<PrimaryButton
isDisabled={loading || isSent}
className="font-normal"