feat: multi language support (#254)
* feat: multitranslational support * - completion of settings page - completion of sidebar * sidebar rerendering bug fix * completion of the lesson navigation & materials page * completion of the course navigation * completion of requested languages * - Implementation of Language Picker - Rename Bug Fixes * checkpoint save * - addition of the language picker to the onboarding. - addition of the language switcher for the lesson notes. * merge completion * initialization of the new branch * add transition.svelte file * lesson note bug fix * feat: add translation migration * completion of translation * fix height issue of dropdown * refactoring * fix: improve code with LOCALE from db * finally get it fully functional * connect with AI * backward compatibility for lesson note * preserve old content, when generating AI content * undo translations in upgrade modal * fix duplicate key issue from codacy trigger --------- Co-authored-by: Rotimi Best <best@classroomio.com>
This commit is contained in:
co-authored by
Rotimi Best
parent
ff7d2cee35
commit
d75e2f258e
@@ -10,6 +10,13 @@
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import { NavClasses } from '$lib/utils/constants/reusableClass';
|
||||
import { sideBar } from '$lib/components/Org/store';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
interface sideLinks {
|
||||
name: string;
|
||||
icon: any;
|
||||
link: string;
|
||||
}
|
||||
|
||||
function isActive(pagePath: string, itemPath: string) {
|
||||
const pageLinkItems = pagePath.split('/');
|
||||
@@ -21,24 +28,27 @@
|
||||
|
||||
return pagePath.includes(itemPath);
|
||||
}
|
||||
let sideLinks = [
|
||||
|
||||
let sideLinks: sideLinks[] = [];
|
||||
|
||||
$: sideLinks = [
|
||||
{
|
||||
name: 'Home',
|
||||
name: $t('lms_navigation.home'),
|
||||
icon: HomeIcon,
|
||||
link: '/lms'
|
||||
},
|
||||
{
|
||||
name: 'My Learning',
|
||||
name: $t('lms_navigation.my_learning'),
|
||||
icon: CourseIcon,
|
||||
link: '/lms/mylearning'
|
||||
},
|
||||
{
|
||||
name: 'Exercise',
|
||||
name: $t('lms_navigation.exercise'),
|
||||
icon: LicenseDraft,
|
||||
link: '/lms/exercises'
|
||||
},
|
||||
{
|
||||
name: 'Community',
|
||||
name: $t('lms_navigation.community'),
|
||||
icon: CommunityIcon,
|
||||
link: '/lms/community'
|
||||
}
|
||||
@@ -98,7 +108,7 @@
|
||||
<a href="/lms" class="text-black" on:click={toggleSidebar}>
|
||||
<li class="flex items-center py-3 px-4 mb-2 rounded">
|
||||
<HelpIcon size={20} class="carbon-icon dark:text-white" />
|
||||
<p class="dark:text-white ml-2">Help</p>
|
||||
<p class="dark:text-white ml-2">{$t('lms_navigation.help')}</p>
|
||||
</li>
|
||||
</a>
|
||||
<a href="/lms/settings" class="text-black" on:click={toggleSidebar}>
|
||||
@@ -109,7 +119,7 @@
|
||||
) && NavClasses.active}"
|
||||
>
|
||||
<Settings size={20} class="carbon-icon dark:text-white" />
|
||||
<p class="dark:text-white ml-2">Settings</p>
|
||||
<p class="dark:text-white ml-2">{$t('lms_navigation.settings')}</p>
|
||||
</li>
|
||||
</a>
|
||||
</ul>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import CoursesEmptyIcon from '$lib/components/Icons/CoursesEmptyIcon.svelte';
|
||||
import { coursesInProgress } from '$lib/components/Courses/store';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
const gotoCourse = (id: string | undefined) => {
|
||||
if (!id) return;
|
||||
@@ -12,7 +13,9 @@
|
||||
</script>
|
||||
|
||||
<section class="h-full">
|
||||
<p class="text-base font-semibold text-[#040F2D] pb-3 dark:text-white">Currently Learning</p>
|
||||
<p class="text-base font-semibold text-[#040F2D] pb-3 dark:text-white">
|
||||
{$t('dashboard.current_lesson')}
|
||||
</p>
|
||||
<div
|
||||
class="flex items-center flex-col border border-[#EAEAEA] dark:bg-neutral-800 gap-2 rounded w-full lg:h-[40vh] p-3"
|
||||
>
|
||||
@@ -33,7 +36,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<PrimaryButton
|
||||
label="Continue"
|
||||
label={$t('dashboard.continue')}
|
||||
variant={VARIANTS.OUTLINED}
|
||||
className="rounded-none text-[#0233BD]"
|
||||
onClick={() => gotoCourse(course.id)}
|
||||
@@ -53,8 +56,8 @@
|
||||
{:else}
|
||||
<div class="flex flex-col items-center">
|
||||
<CoursesEmptyIcon />
|
||||
<h3 class="text-center">No courses in progress yet</h3>
|
||||
<p class="w-[75%] text-center">Once you start a course, your progress will reflect here</p>
|
||||
<h3 class="text-center">{$t('dashboard.no_courses')}</h3>
|
||||
<p class="w-[75%] text-center">{$t('dashboard.start_course')}</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -9,9 +9,14 @@
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import LogoutButton from '$lib/components/Buttons/Logout/index.svelte';
|
||||
import generateUUID from '$lib/utils/functions/generateUUID';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import LanguagePicker from '$lib/components/Org/Settings/LanguagePicker.svelte';
|
||||
import { handleLocaleChange } from '$lib/utils/functions/translations';
|
||||
|
||||
let avatar = '';
|
||||
let loading = false;
|
||||
let hasLangChanged = false;
|
||||
let locale = '';
|
||||
|
||||
async function handleUpdate() {
|
||||
try {
|
||||
@@ -20,7 +25,8 @@
|
||||
const updates = {
|
||||
fullname: $profile.fullname,
|
||||
username: $profile.username,
|
||||
email: $profile.email
|
||||
email: $profile.email,
|
||||
locale
|
||||
};
|
||||
|
||||
if (avatar) {
|
||||
@@ -47,25 +53,31 @@
|
||||
}));
|
||||
snackbar.success();
|
||||
|
||||
if (hasLangChanged) {
|
||||
handleLocaleChange(locale);
|
||||
}
|
||||
|
||||
if (error) throw error;
|
||||
} catch (error) {
|
||||
let message = error.message;
|
||||
if (message.includes('profile_username_key')) {
|
||||
message = 'username already exists';
|
||||
message = $t('username already exists');
|
||||
}
|
||||
|
||||
snackbar.error(`Update failed: ${message}`);
|
||||
snackbar.error(`snackbar.lms.error.update: ${message}`);
|
||||
loading = false;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
$: locale = $profile.locale;
|
||||
</script>
|
||||
|
||||
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5">
|
||||
<Row class="flex flex-col lg:flex-row items-center lg:items-start py-7 border-bottom-c ">
|
||||
<Column sm={4} md={8} lg={4} class="mt-2 md:mt-0">
|
||||
<p class="dark:text-white font-bold">Profile Picture</p>
|
||||
<p class="dark:text-white font-bold">{$t('settings.profile.profile_picture.heading')}</p>
|
||||
</Column>
|
||||
<Column sm={2} md={4} lg={8} class="mt-2 lg:mt-0">
|
||||
<UploadImage bind:avatar src={$profile.avatar_url} widthHeight="w-16 h-16 lg:w-24 lg:h-24" />
|
||||
@@ -73,17 +85,35 @@
|
||||
</Row>
|
||||
<Row class="flex flex-col lg:flex-row py-7 border-bottom-c">
|
||||
<Column sm={4} md={4} lg={4}>
|
||||
<p class="dark:text-white font-bold">Personal Information</p>
|
||||
<p class="dark:text-white font-bold">{$t('settings.profile.personal_information.heading')}</p>
|
||||
</Column>
|
||||
<Column sm={8} md={8} lg={8} class="mt-2 lg:mt-0">
|
||||
<TextField label="Full Name" bind:value={$profile.fullname} className="w-full lg:w-60 mb-4" />
|
||||
<TextField label="Username" bind:value={$profile.username} className="w-full lg:w-60 mb-4" />
|
||||
<TextField label="Email" bind:value={$profile.email} className="w-full lg:w-60 mb-4" />
|
||||
<TextField
|
||||
label={$t('settings.profile.personal_information.full_name')}
|
||||
bind:value={$profile.fullname}
|
||||
className="w-full lg:w-60 mb-4"
|
||||
/>
|
||||
<TextField
|
||||
label={$t('settings.profile.personal_information.username')}
|
||||
bind:value={$profile.username}
|
||||
className="w-full lg:w-60 mb-4"
|
||||
/>
|
||||
<TextField
|
||||
label={$t('settings.profile.personal_information.email')}
|
||||
bind:value={$profile.email}
|
||||
className="w-full lg:w-60 mb-4"
|
||||
/>
|
||||
|
||||
<LanguagePicker
|
||||
bind:hasLangChanged
|
||||
bind:value={$profile.locale}
|
||||
className="w-full lg:w-60 mb-4"
|
||||
/>
|
||||
</Column>
|
||||
</Row>
|
||||
<Row class="m-5 w-full flex items-center gap-2 lg:justify-center">
|
||||
<PrimaryButton
|
||||
label="Update Profile"
|
||||
label={$t('settings.profile.update_profile')}
|
||||
variant={VARIANTS.CONTAINED_DARK}
|
||||
className="mr-5"
|
||||
isLoading={loading}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { isMobile } from '$lib/utils/store/useMobile';
|
||||
import { formatDate } from '$lib/utils/functions/routes/dashboard';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
let store;
|
||||
|
||||
@@ -26,7 +27,7 @@
|
||||
</script>
|
||||
|
||||
<section class="w-full h-full mt-2">
|
||||
<p class="text-base font-semibold text-[#040F2D] pb-3 dark:text-white">Your Schedule</p>
|
||||
<p class="text-base font-semibold text-[#040F2D] pb-3 dark:text-white">{$t('schedule.title')}</p>
|
||||
|
||||
<div
|
||||
class="flex items-center flex-col border border-[#EAEAEA] dark:bg-neutral-800 rounded w-full h-fit lg:h-[40vh] p-3 overflow-y-auto"
|
||||
@@ -51,23 +52,23 @@
|
||||
{formatDate($store?.selected)}
|
||||
</p>
|
||||
{/if}
|
||||
{#if formatDate($store?.selected) == 'Today'}
|
||||
{#if formatDate($store?.selected) == $t('dashboard.today')}
|
||||
<span class="flex flex-col md:flex-row items-baseline justify-center gap-10 mt-5">
|
||||
<p class="text-xs font-normal text-[#656565] dark:text-white">2:30pm</p>
|
||||
<div class="flex flex-col items-start gap-4">
|
||||
<span>
|
||||
<p class="text-lg font-semibold text-[#262626] dark:text-white">
|
||||
The essence of design collaboration
|
||||
{$t('schedule.essence')}
|
||||
</p>
|
||||
<a href="#" class="text-sm font-normal text-primary-700 dark:text-white"
|
||||
>Figma Introduction</a
|
||||
>{$t('schedule.figma')}</a
|
||||
>
|
||||
</span>
|
||||
<PrimaryButton label="Join Call" variant={VARIANTS.OUTLINED} className="" />
|
||||
<PrimaryButton label={$t('schedule.join')} variant={VARIANTS.OUTLINED} className="" />
|
||||
</div>
|
||||
</span>
|
||||
{:else}
|
||||
<p>No Lesson on this day</p>
|
||||
<p>{$t('schedule.no')}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user