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:
Adeyemi Adetunji
2024-04-19 05:45:48 +01:00
committed by GitHub
co-authored by Rotimi Best
parent ff7d2cee35
commit d75e2f258e
153 changed files with 11264 additions and 1130 deletions
+13 -8
View File
@@ -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="">