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 <[email protected]>
This commit is contained in:
co-authored by
Rotimi Best
parent
ff7d2cee35
commit
d75e2f258e
@@ -9,6 +9,7 @@
|
||||
import { courses, courseMetaDeta } from '$lib/components/Courses/store';
|
||||
import type { Course } from '$lib/utils/types';
|
||||
import { getGreeting } from '$lib/utils/functions/date';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
let hasFetched = false;
|
||||
let progressPercentage = 0;
|
||||
@@ -52,7 +53,7 @@
|
||||
<section class="max-w-6xl gap-5 mx-auto">
|
||||
<div class="m-5">
|
||||
<h1 class="dark:text-white text-2xl md:text-3xl font-bold mb-3">
|
||||
{getGreeting()}
|
||||
{$t(getGreeting())}
|
||||
{$profile.fullname}!
|
||||
</h1>
|
||||
<div
|
||||
@@ -60,11 +61,10 @@
|
||||
>
|
||||
<span>
|
||||
<p class="w-full md:w-[75%] lg:w-[80%] text-white text-xs lg:text-xl font-normal mb-5">
|
||||
Today is another day to bring you closer to your learning goals. Don’t give up, the more
|
||||
you learn the better you get.
|
||||
{$t('dashboard.lms_dashboard_hero')}
|
||||
</p>
|
||||
<PrimaryButton
|
||||
label="Don't give up"
|
||||
label={$t('dashboard.dont')}
|
||||
variant={VARIANTS.CONTAINED_WHITE}
|
||||
onClick={() => goto('/lms/mylearning')}
|
||||
/>
|
||||
@@ -80,7 +80,9 @@
|
||||
<Learning />
|
||||
</div>
|
||||
<div class="mt-10 xl:mt-2 w-full lg:w-[50%]">
|
||||
<p class="text-base font-semibold text-[#040F2D] pb-3 dark:text-white">Your Progress</p>
|
||||
<p class="text-base font-semibold text-[#040F2D] pb-3 dark:text-white">
|
||||
{$t('dashboard.your_progress')}
|
||||
</p>
|
||||
<div
|
||||
class="flex items-center justify-center border border-[#EAEAEA] dark:bg-neutral-800 gap-2 rounded h-fit lg:h-[40vh] lg:overflow-y-auto p-3"
|
||||
>
|
||||
@@ -92,14 +94,17 @@
|
||||
</div>
|
||||
<span class="text-center xl:text-start">
|
||||
<p class="text-base font-semibold py-2 text-[#040F2D] dark:text-white">
|
||||
Your Progress
|
||||
{$t('dashboard.your_progress')}
|
||||
</p>
|
||||
{#if totalLessons > 0}
|
||||
<p class="text-xs font-normal text-[#656565] dark:text-white">
|
||||
{totalCompleted}/{totalLessons} lessons completed
|
||||
{totalCompleted}/{totalLessons}
|
||||
{$t('dashboard.lessons_completed')}
|
||||
</p>
|
||||
{:else}
|
||||
<p class="text-xs font-normal text-[#656565] dark:text-white">No courses started</p>
|
||||
<p class="text-xs font-normal text-[#656565] dark:text-white">
|
||||
{$t('dashboard.No_courses_started')}
|
||||
</p>
|
||||
{/if}
|
||||
</span>
|
||||
<h1
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import Community from '$lib/components/Org/Community/index.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
function askCommunity() {
|
||||
goto(`/lms/community/ask`);
|
||||
@@ -15,8 +16,8 @@
|
||||
<section class="w-full max-w-4xl mx-auto">
|
||||
<div class="py-10 px-5">
|
||||
<div class="flex items-center justify-between mb-10">
|
||||
<h1 class="dark:text-white text-3xl font-bold">Community</h1>
|
||||
<PrimaryButton label="Ask Community" onClick={askCommunity} />
|
||||
<h1 class="dark:text-white text-3xl font-bold">{$t('community.title')}</h1>
|
||||
<PrimaryButton label={$t('community.ask_button')} onClick={askCommunity} />
|
||||
</div>
|
||||
|
||||
<Community isLMS={true} />
|
||||
|
||||
@@ -178,11 +178,11 @@
|
||||
|
||||
if (error) {
|
||||
console.error('Error: commenting', error);
|
||||
snackbar.error('Error - Please try again later');
|
||||
snackbar.error('snackbar.community.error.try_again');
|
||||
} else {
|
||||
console.log('Success: commenting', data);
|
||||
|
||||
snackbar.success('Comment Submitted!');
|
||||
snackbar.success('snackbar.community.success.comment_submitted');
|
||||
|
||||
// Add to comment
|
||||
const _c = data?.[0];
|
||||
@@ -230,7 +230,7 @@
|
||||
const { error } = await supabase.from(table).update({ votes }).match({ id: matchId });
|
||||
if (error) {
|
||||
console.error('Error: upvoteQuestion', error);
|
||||
snackbar.error('Error - Please try again later');
|
||||
snackbar.error('snackbar.community.error.try_again');
|
||||
} else {
|
||||
if (isQuestion) {
|
||||
voted.question = true;
|
||||
@@ -263,13 +263,14 @@
|
||||
const { error } = await supabase
|
||||
.from('community_question')
|
||||
.update({
|
||||
...editContent,
|
||||
title: editContent.title,
|
||||
body: editContent.body,
|
||||
course_id: editContent.courseId
|
||||
})
|
||||
.match({ id: question.id });
|
||||
if (error) {
|
||||
console.error('Error: handleQuestionEdit', error);
|
||||
snackbar.error('Error - Please try again later');
|
||||
snackbar.error('snackbar.community.error.try_again');
|
||||
} else {
|
||||
question.title = editContent.title;
|
||||
question.body = editContent.body;
|
||||
@@ -298,11 +299,11 @@
|
||||
deleteComment.isDeleting = false;
|
||||
|
||||
if (error) {
|
||||
snackbar.error('Error deleting comments');
|
||||
snackbar.error('snackbar.community.error.deleting_comments');
|
||||
console.log('Error deleting comments', error);
|
||||
return;
|
||||
}
|
||||
snackbar.success('Deleted successfully');
|
||||
snackbar.success('snackbar.community.success.success_delete');
|
||||
|
||||
question.comments = question.comments.filter((c) => c.id !== deleteComment.commentId);
|
||||
deleteComment.shouldDelete = false;
|
||||
@@ -319,7 +320,7 @@
|
||||
.match({ question_id: deleteQuestion.questionId });
|
||||
|
||||
if (commentDeleteError) {
|
||||
snackbar.error('Error deleting comments');
|
||||
snackbar.error('snackbar.community.error.deleting_comments');
|
||||
console.log('Error deleting comments', commentDeleteError);
|
||||
|
||||
deleteQuestion.isDeleting = false;
|
||||
@@ -332,12 +333,12 @@
|
||||
.match({ id: deleteQuestion.questionId });
|
||||
|
||||
if (questionDeleteError) {
|
||||
snackbar.error('Error deleting question');
|
||||
snackbar.error('snackbar.community.error.deleting_question');
|
||||
console.log('Error deleting question', questionDeleteError);
|
||||
return;
|
||||
}
|
||||
|
||||
snackbar.success('Deleted successfully');
|
||||
snackbar.success('snackbar.community.success.success_delete');
|
||||
goto(`/lms/community`);
|
||||
deleteQuestion.isDeleting = false;
|
||||
}
|
||||
@@ -394,7 +395,7 @@
|
||||
errorMessage={errors.title}
|
||||
/>
|
||||
<Dropdown
|
||||
class="w-[25%] h-fit"
|
||||
class="w-[25%] h-full"
|
||||
size="xl"
|
||||
label="Select Course"
|
||||
items={fetchedCourses.map((course) => ({ id: course.id, text: course.title }))}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import { profile } from '$lib/utils/store/user';
|
||||
import { fetchCourses } from '$lib/components/Courses/api';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
let errors: {
|
||||
title?: string;
|
||||
@@ -57,7 +58,7 @@
|
||||
} else {
|
||||
const slug = question[0]?.slug;
|
||||
console.log('Success: asking question in lms', question, slug);
|
||||
snackbar.success('Question Submitted!');
|
||||
snackbar.success('snackbar.community.success.question_submitted');
|
||||
|
||||
goto(`/lms/community/${slug}`);
|
||||
}
|
||||
@@ -77,25 +78,26 @@
|
||||
<section class="w-full max-w-3xl mx-auto">
|
||||
<div class="p-5">
|
||||
<a class="text-gray-500 dark:text-white text-md flex items-center" href={`/lms/community`}>
|
||||
<ArrowLeftIcon size={24} class="carbon-icon dark:text-white" /> Go Back
|
||||
<ArrowLeftIcon size={24} class="carbon-icon dark:text-white" />
|
||||
{$t('community.ask.go_back')}
|
||||
</a>
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="dark:text-white text-3xl font-bold">Ask the community</h1>
|
||||
<PrimaryButton label="Publish" onClick={handleSave} />
|
||||
<h1 class="dark:text-white text-3xl font-bold">{$t('community.ask.ask_the')}</h1>
|
||||
<PrimaryButton label={$t('community.ask.publish')} onClick={handleSave} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 p-2 flex gap-x-5 justify-between">
|
||||
<TextField
|
||||
bind:value={fields.title}
|
||||
placeholder="Title"
|
||||
placeholder={$t('community.ask.title')}
|
||||
errorMessage={errors.title}
|
||||
className="w-[75%]"
|
||||
/>
|
||||
<Dropdown
|
||||
class="w-[25%]"
|
||||
class="w-[25%] h-full"
|
||||
size="xl"
|
||||
label="Select Course"
|
||||
label={$t('community.ask.select_course')}
|
||||
invalid={!!errors.courseId}
|
||||
invalidText={errors.courseId}
|
||||
items={fetchedCourses.map((course) => ({ id: course.id, text: course.title }))}
|
||||
@@ -105,7 +107,7 @@
|
||||
|
||||
<TextEditor
|
||||
bind:value={fields.body}
|
||||
placeholder="Ask the community any question you might have"
|
||||
placeholder={$t('community.ask.ask_community')}
|
||||
onChange={(html) => (fields.body = html)}
|
||||
/>
|
||||
</section>
|
||||
|
||||
@@ -8,29 +8,30 @@
|
||||
import { fetchLMSExercises } from '$lib/utils/services/lms/exercises';
|
||||
import type { LMSExercise } from '$lib/utils/services/lms/exercises';
|
||||
import { calDateDiff } from '$lib/utils/functions/date';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
const defaultSections = [
|
||||
{
|
||||
id: 0,
|
||||
title: 'Not Submitted',
|
||||
title: $t('exercises.not_submitted'),
|
||||
items: [],
|
||||
className: 'text-[#E35353] bg-[#FDDFE4]'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: 'Submitted',
|
||||
title: $t('exercises.submitted'),
|
||||
items: [],
|
||||
className: 'text-orange-700 bg-orange-200'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Grading In Progress',
|
||||
title: $t('exercises.in_progress'),
|
||||
items: [],
|
||||
className: 'text-yellow-700 bg-yellow-200'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Graded',
|
||||
title: $t('exercises.graded'),
|
||||
value: 0,
|
||||
items: [],
|
||||
className: 'text-green-700 bg-green-200'
|
||||
@@ -105,7 +106,7 @@
|
||||
console.log('error', error);
|
||||
|
||||
if (error) {
|
||||
snackbar.error('Fetching exercises failed, please reload the page');
|
||||
snackbar.error('snackbar.exercise.error_fetching');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -123,7 +124,7 @@
|
||||
<section class="w-full max-w-6xl mx-auto">
|
||||
<div class="p-5">
|
||||
<div class="flex items-center justify-between mb-10">
|
||||
<h1 class="dark:text-white text-3xl font-bold">Exercises</h1>
|
||||
<h1 class="dark:text-white text-3xl font-bold">{$t('exercises.heading')}</h1>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -156,7 +157,7 @@
|
||||
href={item.lessonURL}
|
||||
>
|
||||
<p class="dark:text-white text-grey text-sm">
|
||||
Lesson: <span class="italic">{item.lessonTitle}</span>
|
||||
{$t('exercises.lesson')} <span class="italic">{item.lessonTitle}</span>
|
||||
</p>
|
||||
</a>
|
||||
<p class="dark:text-white text-gray-500 text-xs">
|
||||
|
||||
@@ -8,14 +8,15 @@
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
import { courses, courseMetaDeta, coursesComplete } from '$lib/components/Courses/store';
|
||||
import { browser } from '$app/environment';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: 'In progress',
|
||||
label: $t('my_learning.progress'),
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: 'Complete',
|
||||
label: $t('my_learning.complete'),
|
||||
value: 2
|
||||
}
|
||||
];
|
||||
@@ -55,23 +56,23 @@
|
||||
<section class="max-w-6xl mx-auto">
|
||||
<div class="m-2 md:m-5">
|
||||
<div role="searchbox" class=" bg-gray-100 w-full md:w-[60%] lg:w-[30%]">
|
||||
<Search placeholder="Search courses" class="dark:text-black" />
|
||||
<Search placeholder={$t('my_learning.search')} class="dark:text-black" />
|
||||
</div>
|
||||
<h1 class="text-3xl font-semibold my-4">My Learning</h1>
|
||||
<h1 class="text-3xl font-semibold my-4">{$t('my_learning.heading')}</h1>
|
||||
<Tabs {tabs} {currentTab} {onChange}>
|
||||
<slot:fragment slot="content">
|
||||
<TabContent value={tabs[0].value} index={currentTab}>
|
||||
<Courses
|
||||
courses={$courses}
|
||||
emptyTitle="No Course In progress"
|
||||
emptyDescription="Any course that you start will be displayed here"
|
||||
emptyTitle={$t('my_learning.not_in_progress')}
|
||||
emptyDescription={$t('my_learning.any_progress')}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent value={tabs[1].value} index={currentTab}>
|
||||
<Courses
|
||||
courses={$coursesComplete}
|
||||
emptyTitle="No Course Completed"
|
||||
emptyDescription="Any course that you complete will be displayed here"
|
||||
emptyTitle={$t('my_learning.not_completed')}
|
||||
emptyDescription={$t('my_learning.any_course')}
|
||||
/>
|
||||
</TabContent>
|
||||
</slot:fragment>
|
||||
|
||||
@@ -3,18 +3,20 @@
|
||||
import { Tabs, Tab, TabContent } from 'carbon-components-svelte';
|
||||
import Profile from '$lib/components/LMS/components/Profile.svelte';
|
||||
import Integrations from '$lib/components/Org/Settings/Integrations.svelte';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
let selected = 0;
|
||||
const tabs = [
|
||||
let tabs = [];
|
||||
$: tabs = [
|
||||
{
|
||||
key: 0,
|
||||
label: 'Profile',
|
||||
label: $t('settings.tabs.1'),
|
||||
tabKey: '',
|
||||
href: $page.url.pathname
|
||||
},
|
||||
{
|
||||
key: 1,
|
||||
label: 'Integrations',
|
||||
label: $t('settings.tabs.4'),
|
||||
tabKey: 'integrations',
|
||||
href: `${$page.url.pathname}?tab=integrations`,
|
||||
disabled: false
|
||||
@@ -25,7 +27,7 @@
|
||||
<section class="w-full max-w-6xl mx-auto">
|
||||
<div class="py-10 px-5">
|
||||
<div class="flex items-center justify-between mb-10">
|
||||
<h1 class="dark:text-white text-3xl font-bold">Settings</h1>
|
||||
<h1 class="dark:text-white text-3xl font-bold">{$t('settings.heading')}</h1>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
|
||||
Reference in New Issue
Block a user