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:
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
@@ -30,6 +30,8 @@
triggerSendEmail
} from '$lib/utils/services/notification/notification';
import NewsFeedLoader from '$lib/components/Course/components/NewsFeed/NewsFeedLoader.svelte';
import { t } from '$lib/utils/functions/translations.js';
export let data;
let createdComment;
@@ -65,7 +67,7 @@
comment: feed.comment.filter((comment) => comment.id !== id)
}));
return snackbar.success('Comment Deleted');
return snackbar.success('snackbar.course.success.comment_deleted');
};
const addNewReaction = async (reactionType: string, feedId: string, authorId: string) => {
@@ -98,7 +100,7 @@
.eq('id', feedId);
if (response.error) {
return snackbar.error('An error occurred while reacting to news feed');
return snackbar.error('snackbar.course.error.reaction_error');
}
$newsFeed = $newsFeed.map((feed) => {
@@ -118,7 +120,7 @@
});
if (response.error) {
return snackbar.error('An error occurred while creating comment');
return snackbar.error('snackbar.course.error.commenting_error');
}
if (!response.data) return;
@@ -142,7 +144,7 @@
content: comment
};
snackbar.success('Comment added');
snackbar.success('snackbar.course.success.comment_added');
return {
...feed,
@@ -159,12 +161,16 @@
const { response } = await toggleFeedIsPinned(feedId, newIsPinned);
if (response.error) {
return snackbar.error('Failed to toggle pinned feed');
return snackbar.error('snackbar.course.error.toggle_error');
}
$newsFeed = $newsFeed.map((feed) => {
if (feed.id === feedId) {
snackbar.success(`${newIsPinned ? 'Pinned' : 'Unpinned'} Successfully`);
snackbar.success(
`${
newIsPinned ? 'snackbar.course.success.pinned' : 'snackbar.course.success.unpinned'
} snackbar.course.success.successfully`
);
feed.isPinned = newIsPinned;
return feed;
@@ -178,7 +184,7 @@
deleteNewsFeed(id);
const deletedFeed = $newsFeed.filter((feed) => feed.id !== id);
snackbar.success('Feed deleted successfully');
snackbar.success('snackbar.course.success.feed_delete_success');
$newsFeed = deletedFeed;
};
@@ -189,7 +195,7 @@
isLoading = true;
const { data, error } = await fetchNewsFeeds(courseId);
if (error) {
snackbar.error('Failed to fetch news feeds');
snackbar.error('snackbar.course.error.news_feed_fail');
}
if (data) {
$newsFeed = data.map((feedItem) => ({
@@ -198,7 +204,7 @@
}));
}
} catch (error) {
snackbar.error('An error occured while fetching feed');
snackbar.error('snackbar.course.error.feed_fetch_error');
} finally {
isLoading = false;
}
@@ -223,10 +229,14 @@
</script>
<CourseContainer bind:courseId={data.courseId}>
<PageNav title="News Feed" disableSticky={true}>
<PageNav title={$t('course.navItem.news_feed.heading')} disableSticky={true}>
<slot:fragment slot="widget">
<RoleBasedSecurity allowedRoles={[1, 2]}>
<PrimaryButton className="mr-2" label="Add" onClick={() => ($isNewFeedModal.open = true)} />
<PrimaryButton
className="mr-2"
label={$t('course.navItem.news_feed.heading_button.title')}
onClick={() => ($isNewFeedModal.open = true)}
/>
</RoleBasedSecurity>
</slot:fragment>
</PageNav>
@@ -254,9 +264,9 @@
<Box>
<div class="flex justify-between flex-col items-center w-[90%] md:w-96">
<img src="/images/empty-lesson-icon.svg" alt="Lesson" class="my-2.5 mx-auto" />
<h2 class="text-xl my-1.5 font-normal">No post yet</h2>
<h2 class="text-xl my-1.5 font-normal">{$t('course.navItem.news_feed.body_header')}</h2>
<p class="text-sm text-center text-slate-500">
Make a post to your class. Start by clicking on the Add button.
{$t('course.navItem.news_feed.body_content')}
</p>
</div>
</Box>
@@ -266,7 +276,7 @@
<div class="flex items-center gap-2 mb-3">
<PinFilled size={16} />
<p class="text-sm">Pinned</p>
<p class="text-sm">{$t('course.navItem.news_feed.pinned')}</p>
</div>
{/if}
<NewsFeedCard
@@ -25,6 +25,7 @@
import type { GroupPerson, Lesson } from '$lib/utils/types/index';
import { browser } from '$app/environment';
import { globalStore } from '$lib/utils/store/app';
import { t } from '$lib/utils/functions/translations.js';
export let data;
@@ -91,7 +92,7 @@
takeAttendance(_data).then((res) => {
if (res.error) {
console.error(`res.error`, res.error);
snackbar.error("Something isn't right. Please reload and take attendance again");
snackbar.error('snackbar.attendance.error.reload');
} else {
// console.log('res', JSON.stringify(res));
const { id, is_present } = res.data[0];
@@ -137,7 +138,7 @@
</script>
<CourseContainer bind:courseId={data.courseId}>
<PageNav title="Attendance" />
<PageNav title={$t('course.navItem.attendance.title')} />
<PageBody width="w-full max-w-6xl md:w-11/12">
<section class="flex items-center mx-2 lg:mx-9 my-5">
<div
@@ -145,16 +146,18 @@
>
<div class="flex">
<p class="flex items-center mr-5">
<Checkbox checked disabled /> Present
<Checkbox checked disabled />
{$t('course.navItem.attendance.present')}
</p>
<p class="flex items-center">
<Checkbox disabled /> Absent
<Checkbox disabled />
{$t('course.navItem.attendance.absent')}
</p>
</div>
<div>
<Search
class="dark:text-slate-950"
placeholder="Search students"
placeholder={$t('course.navItem.attendance.search_students')}
bind:value={searchValue}
/>
</div>
@@ -166,10 +169,14 @@
<!-- Moved the lesson headers outside the students loop -->
<StructuredListHead class="bg-primary-100">
<StructuredListRow head class="mx-7">
<StructuredListCell head class="text-primary-600 py-3">Student</StructuredListCell>
<StructuredListCell head class="text-primary-600 py-3"
>{$t('course.navItem.attendance.student')}</StructuredListCell
>
{#each $lessons as lesson, index}
<StructuredListCell head class="text-primary-600 py-3"
>Lesson 0{getLectureNo(index + 1)}</StructuredListCell
>{$t('course.navItem.attendance.lesson')} 0{getLectureNo(
index + 1
)}</StructuredListCell
>
{/each}
</StructuredListRow>
@@ -206,7 +213,9 @@
{#if students.length === 0}
<Box className="h-[300px] w-full">
<AudioConsoleIcon size={32} class="carbon-icon w-80" />
<h3 class="text-3xl text-gray-500 dark:text-white text-center">No Student Added</h3>
<h3 class="text-3xl text-gray-500 dark:text-white text-center">
{$t('course.navItem.attendance.no_student')}
</h3>
</Box>
{/if}
{#if students.length !== 0}
@@ -11,6 +11,7 @@
// import { Tabs, Tab } from 'carbon-components-svelte';
// import TabContent from 'carbon-components-svelte/src/Tabs/TabContent.svelte';
import StudentCertificate from '$lib/components/Course/components/Ceritficate/StudentCertificate/Index.svelte';
import { t } from '$lib/utils/functions/translations';
export let courseId: string = '';
</script>
@@ -18,7 +19,7 @@
<IssueCertificateModal />
<CourseContainer bind:courseId>
<PageNav title="Certificates" disableSticky={true}>
<PageNav title={$t('course.navItem.certificates.title')} disableSticky={true}>
<slot:fragment slot="widget">
<RoleBasedSecurity allowedRoles={[1, 2]}>
<!-- Hide Functionality For V1 -->
@@ -26,6 +26,7 @@
import { course, group } from '$lib/components/Course/store';
import DateField from '$lib/components/Form/Date.svelte';
import type { Lesson } from '$lib/utils/types';
import { t } from '$lib/utils/functions/translations.js';
import { goto } from '$app/navigation';
export let data;
@@ -123,10 +124,14 @@
<!-- TODO: Refactor usage of two way binding isStudent, rather use $globalStore.isStudent -->
<CourseContainer bind:isFetching bind:isStudent bind:courseId={data.courseId}>
<PageNav title="Lessons">
<PageNav title={$t('course.navItem.lessons.heading')}>
<div slot="widget">
<RoleBasedSecurity allowedRoles={[1, 2]}>
<PrimaryButton label="Add" onClick={addLesson} isDisabled={!!lessonEditing} />
<PrimaryButton
label={$t('course.navItem.lessons.add_lesson.button_title')}
onClick={addLesson}
isDisabled={!!lessonEditing}
/>
</RoleBasedSecurity>
</div>
</PageNav>
@@ -206,7 +211,9 @@
className="sm:my-1 w-[100%]"
/>
{:else if !lesson.profile}
<p class="mb-3 ml-2 text-sm dark:text-white">No tutor added</p>
<p class="dark:text-white ml-2 text-sm mb-3">
{$t('course.navItem.lessons.no_tutor')}
</p>
{:else}
<a href="." class="mb-2 flex items-center hover:underline">
<Avatar
@@ -256,7 +263,9 @@
href={lesson.call_url || '#'}
target="_blank"
>
{lesson.call_url ? 'Join lesson' : 'No link'}
{lesson.call_url
? $t('course.navItem.lessons.join_lesson')
: $t('course.navItem.lessons.no_link')}
</a>
</div>
{/if}
@@ -270,14 +279,18 @@
<OverflowMenu size="xl" class="absolute right-0 top-0">
<OverflowMenuItem
disabled={isStudent}
text={lesson.is_unlocked ? 'Lock' : 'Unlock'}
text={lesson.is_unlocked
? $t('course.navItem.lessons.add_lesson.lock')
: $t('course.navItem.lessons.add_lesson.unlock')}
on:click={() => {
lesson.is_unlocked = !lesson.is_unlocked;
handleSaveLesson(lesson, $course.id);
}}
/>
<OverflowMenuItem
text={lessonEditing === lesson.id ? 'Save' : 'Edit'}
text={lessonEditing === lesson.id
? $t('course.navItem.lessons.add_lesson.save')
: $t('course.navItem.lessons.add_lesson.edit')}
on:click={() => {
if (lessonEditing === lesson.id) {
lessonEditing = undefined;
@@ -287,7 +300,11 @@
}
}}
/>
<OverflowMenuItem danger text="Delete" on:click={() => handleDelete(lesson.id)} />
<OverflowMenuItem
danger
text={$t('course.navItem.lessons.add_lesson.delete')}
on:click={() => handleDelete(lesson.id)}
/>
</OverflowMenu>
</RoleBasedSecurity>
</div>
@@ -296,10 +313,9 @@
<Box>
<div class="flex justify-between flex-col items-center w-[90%] md:w-96">
<img src="/images/empty-lesson-icon.svg" alt="Lesson" class="my-2.5 mx-auto" />
<h2 class="text-xl my-1.5 font-normal">No lessons yet</h2>
<h2 class="text-xl my-1.5 font-normal">{$t('course.navItem.lessons.body_header')}</h2>
<p class="text-sm text-center text-slate-500">
Share your knowledge with the world by creating engaging lessons. Start by clicking
on the Add button.
{$t('course.navItem.lessons.body_content')}
</p>
</div>
</Box>
@@ -307,12 +323,11 @@
</section>
{:else}
<Box className="w-full lg:w-11/12 lg:px-4 m-auto">
<div class="flex flex-col items-center justify-between">
<img src="/images/empty-lesson-icon.svg" alt="Lesson" class="mx-auto my-2.5" />
<h2 class="my-1.5 text-xl font-normal">No lessons yet</h2>
<p class="text-center text-sm text-slate-500">
Share your knowledge with the world by creating engaging lessons. Start by clicking on
the Add button.
<div class="flex justify-between flex-col items-center">
<img src="/images/empty-lesson-icon.svg" alt="Lesson" class="my-2.5 mx-auto" />
<h2 class="text-xl my-1.5 font-normal">{$t('course.navItem.lessons.body_header')}</h2>
<p class="text-sm text-center text-slate-500">
{$t('course.navItem.lessons.body_content')}
</p>
</div>
</Box>
@@ -12,7 +12,7 @@
import CourseIcon from '$lib/components/Icons/CourseIcon.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import { Loading } from 'carbon-components-svelte';
import { Dropdown, Loading } from 'carbon-components-svelte';
import RoleBasedSecurity from '$lib/components/RoleBasedSecurity/index.svelte';
import PageNav from '$lib/components/PageNav/index.svelte';
import PageBody from '$lib/components/PageBody/index.svelte';
@@ -25,22 +25,25 @@
import { apps } from '$lib/components/Apps/store';
import APPS_CONSTANTS from '$lib/components/Apps/constants';
import IconButton from '$lib/components/IconButton/index.svelte';
import { lesson, lessons } from '$lib/components/Course/components/Lesson/store/lessons';
import {
lesson,
lessons,
lessonByTranslation
} from '$lib/components/Course/components/Lesson/store/lessons';
import { browser } from '$app/environment';
import { currentOrg } from '$lib/utils/store/org';
import { snackbar } from '$lib/components/Snackbar/store';
import type { LessonCompletion } from '$lib/utils/types';
import { profile } from '$lib/utils/store/user';
import { getIsLessonComplete } from '$lib/components/Course/components/Lesson/functions';
import { captureMessage } from '@sentry/browser';
import { t } from '$lib/utils/functions/translations.js';
import { LANGUAGES } from '$lib/utils/constants/translation';
export let data;
let path = '';
let mode = MODES.view;
let prevMode = '';
let prevLessonId = '';
let isFetching = false;
let isMarkingComplete = false;
let isLoading = false;
let isSaving = false;
@@ -58,21 +61,19 @@
}
async function fetchReqData(lessonId = '', isMaterialsTabActive: boolean) {
isFetching = true;
$lesson.isFetching = true;
let lessonData;
if (isMaterialsTabActive) {
const lesson = await fetchLesson(lessonId);
lessonData = lesson.data;
}
console.log('lessonData', lessonData);
prevLessonId = lessonId;
console.log({ lessonData });
const totalExercises = lessonData?.totalExercises?.[0] && lessonData.totalExercises[0].count;
const totalComments = lessonData?.totalComments?.[0] && lessonData.totalComments[0].count;
setLesson(lessonData, totalExercises || 0, totalComments || 0);
isFetching = false;
$lesson.isFetching = false;
}
async function markLessonComplete(lessonId: string) {
@@ -118,7 +119,7 @@
return l;
});
snackbar.success('Marked as complete');
snackbar.success('snackbar.lessons.success.complete_marked');
isMarkingComplete = false;
}
@@ -168,10 +169,10 @@
a.click();
a.remove();
snackbar.success('Download Complete');
snackbar.success('snackbar.lessons.success.complete_download');
} catch (error) {
console.log('error downloading lesson', error);
snackbar.error("Something's not right - Please try later");
snackbar.error('snackbar.lessons.error.try_later');
}
isLoading = false;
@@ -186,6 +187,7 @@
lesson_completion = [...lessonData.lesson_completion];
}
console.log('profile locale', $profile.locale);
lesson.update((l) => ({
...l,
id: data.lessonId,
@@ -197,8 +199,26 @@
slide_url: lessonData.slide_url
},
lesson_completion,
exercises: []
exercises: [],
locale: $profile.locale
}));
if (Array.isArray(lessonData.lesson_language)) {
lessonByTranslation.update((lessLocales) => {
return {
...lessLocales,
[data.lessonId]: lessonData.lesson_language.reduce(
(acc, cur) => {
acc[cur.locale] = cur.content;
return acc;
},
{
en: ''
}
)
};
});
}
}
const toggleApps = () => {
@@ -229,19 +249,19 @@
<CourseContainer
bind:isStudent
{path}
isExercisePage={!data.isMaterialsTabActive && data.exerciseId}
isExercisePage={!data.isMaterialsTabActive && !!data.exerciseId}
bind:courseId={data.courseId}
>
<PageNav
bind:hideOnMobile={isStudent}
navItems={[
{
label: 'Materials',
label: $t('course.navItem.lessons.lesson_nav.materials'),
isActive: data.isMaterialsTabActive,
href: path
},
{
label: 'Exercises',
label: $t('course.navItem.lessons.lesson_nav.exercises'),
badgeValue: data.isMaterialsTabActive ? $lesson.totalExercises : $lesson.exercises.length,
isActive: !data.isMaterialsTabActive,
href: `${path}/exercises`
@@ -249,43 +269,52 @@
]}
>
<svelte:fragment slot="widget">
<RoleBasedSecurity allowedRoles={[1, 2]}>
{#if data.isMaterialsTabActive}
<div class="tab">
<IconButton onClick={toggleApps} buttonClassName="">
<OverflowMenuVertical size={24} />
</IconButton>
</div>
<div
class={`flex-row ${
$apps.dropdown && $apps.open
? 'absolute lg:relative top-[85px] lg:top-0 right-14 lg:right-0 z-40 dark:bg-neutral-800 p-3 lg:p-0'
: 'hidden'
} lg:flex items-center`}
>
{#if $course.metadata.lessonDownload && !!PUBLIC_SERVER_URL}
<div class="flex">
<div class="mr-5">
<Dropdown items={LANGUAGES} bind:selectedId={$lesson.locale} class="h-full" />
</div>
<RoleBasedSecurity allowedRoles={[1, 2]}>
{#if data.isMaterialsTabActive}
<div class="tab">
<IconButton onClick={toggleApps} buttonClassName="">
<OverflowMenuVertical size={24} />
</IconButton>
</div>
<div
class={`flex-row ${
$apps.dropdown && $apps.open
? 'absolute lg:relative top-[85px] lg:top-0 right-14 lg:right-0 z-40 rounded-md bg-gray-100 dark:bg-neutral-800 p-3 lg:p-0'
: 'hidden'
} lg:flex items-center`}
>
<PrimaryButton
className="mb-2 lg:mb-0 mr-2"
variant={VARIANTS.OUTLINED}
onClick={downloadLesson}
{isLoading}
onClick={() => {
$apps.dropdown = false;
toggleMode();
}}
isDisabled={isSaving}
>
<Download size={16} class="mr-2" />
Download PDF
{mode === MODES.edit
? $t('course.navItem.lessons.done')
: $t('course.navItem.lessons.edit')}
</PrimaryButton>
{/if}
<PrimaryButton className="mr-2" variant={VARIANTS.OUTLINED} onClick={toggleMode}>
{#if isSaving}
<Loading withOverlay={false} small />
<span class="text-sm ml-2 italic">Autosaving...</span>
{:else}
{mode === MODES.edit ? 'Done' : 'Edit'}
{#if $course.metadata.lessonDownload && !!PUBLIC_SERVER_URL}
<PrimaryButton
className="mr-"
variant={VARIANTS.OUTLINED}
onClick={downloadLesson}
{isLoading}
>
<Download size={16} />
</PrimaryButton>
{/if}
</PrimaryButton>
</div>
{/if}
</RoleBasedSecurity>
</div>
{/if}
</RoleBasedSecurity>
</div>
</svelte:fragment>
</PageNav>
@@ -330,7 +359,10 @@
{:else}
<CheckmarkOutlineIcon size={24} class="carbon-icon mr-2" />
{/if}
Mark as {isLessonComplete ? 'Incomplete' : 'Complete'}
{$t('course.navItem.lessons.mark_as')}
{isLessonComplete
? $t('course.navItem.lessons.incomplete')
: $t('course.navItem.lessons.complete')}
</PrimaryButton>
</div>
{/if}
@@ -15,6 +15,7 @@
import { course } from '$lib/components/Course/store';
import { snackbar } from '$lib/components/Snackbar/store';
import { globalStore } from '$lib/utils/store/app';
import { t } from '$lib/utils/functions/translations.js';
export let data;
@@ -38,7 +39,7 @@
const { data: marks, error } = await fetchMarks(courseId);
if (error) {
console.error('Error fetching marks', error);
snackbar.error('Error fetching marks');
snackbar.error('snackbar.marks.error');
return;
}
@@ -86,13 +87,13 @@
</script>
<CourseContainer bind:courseId={data.courseId}>
<PageNav title="Marks" />
<PageNav title={$t('course.navItem.marks.title')} />
<PageBody width="w-full max-w-6xl md:w-11/12">
<div class="table rounded-md border border-gray-300 w-full">
<div class="flex items-center {borderBottomGrey}">
<div class="box flex items-center p-3">
<p class="dark:text-white w-40">Student</p>
<p class="dark:text-white w-40">{$t('course.navItem.marks.student')}</p>
</div>
{#each $lessons as lesson, index}
{#if lessonMapping[lesson.id]}
@@ -117,7 +118,7 @@
{/if}
{/each}
<div class="box flex items-center {borderleftGrey}">
<p class="dark:text-white w-20 text-center">Total</p>
<p class="dark:text-white w-20 text-center">{$t('course.navItem.marks.total')}</p>
</div>
</div>
@@ -163,7 +164,9 @@
{:else}
<Box>
<AudioConsoleIcon size={32} class="carbon-icon w-80" />
<h3 class="text-3xl text-gray-500 dark:text-white">No Student Added</h3>
<h3 class="text-3xl text-gray-500 dark:text-white">
{$t('course.navItem.marks.no_student')}
</h3>
</Box>
{/each}
</div>
@@ -30,6 +30,7 @@
import { snackbar } from '$lib/components/Snackbar/store';
import Avatar from '$lib/components/Avatar/index.svelte';
import type { GroupPerson } from '$lib/utils/types';
import { t } from '$lib/utils/functions/translations.js';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
@@ -102,7 +103,7 @@
}
function copyToClipboard(studentAssignedId: string | null) {
snackbar.success(`Copied to clipboard`);
snackbar.success(`snackbar.people.success.copied`);
if (studentAssignedId) {
copy(studentAssignedId);
@@ -125,10 +126,14 @@
/>
<CourseContainer bind:courseId={data.courseId}>
<PageNav title="People" disableSticky={true}>
<PageNav title={$t('course.navItem.people.title')} disableSticky={true}>
<slot:fragment slot="widget">
<RoleBasedSecurity allowedRoles={[1, 2]}>
<PrimaryButton className="mr-2" label="Add" onClick={handleClick} />
<PrimaryButton
className="mr-2"
label={$t('course.navItem.people.add')}
onClick={handleClick}
/>
</RoleBasedSecurity>
</slot:fragment>
</PageNav>
@@ -140,14 +145,14 @@
<div class="max-w-[320px]">
<Search
class="dark:text-slate-950 border-0 bg-zinc-100 w-full"
placeholder="Search people"
placeholder={$t('course.navItem.people.search')}
bind:value={searchValue}
/>
</div>
<div class="mb-3">
<Select
bind:value={filterBy}
options={ROLES}
options={ROLES.map((role) => ({ label: $t(role.label), value: role.value }))}
className="dark:text-black mt-3 max-w-[80px]"
/>
<!-- <select bind:value={filterBy} class="mt-3">
@@ -167,13 +172,13 @@
>
<StructuredListRow head class="mx-7">
<StructuredListCell head class="text-primary-700 py-3 dark:text-white"
>Name</StructuredListCell
>{$t('course.navItem.people.name')}</StructuredListCell
>
<StructuredListCell head class="text-primary-700 py-3 dark:text-white"
>Role</StructuredListCell
>{$t('course.navItem.people.role')}</StructuredListCell
>
<StructuredListCell head class="text-primary-700 py-3 dark:text-white"
>Action</StructuredListCell
>{$t('course.navItem.people.action')}</StructuredListCell
>
<RoleBasedSecurity allowedRoles={[1, 2]}>
<p class="dark:text-white hidden lg:block text-lg w-20" />
@@ -212,7 +217,7 @@
/>
</RoleBasedSecurity>
{#if person.profile_id == $profile.id}
<ComingSoon label="You" />
<ComingSoon label={$t('course.navItem.people.you')} />
{/if}
</div>
</div>
@@ -232,11 +237,14 @@
</a>
<div class="flex items-center justify-between">
<RoleBasedSecurity allowedRoles={[1, 2]}>
<CopyButton text={getEmail(person)} feedback="Copied Email to clipboard" />
<CopyButton
text={getEmail(person)}
feedback={$t('course.navItem.people.feedback')}
/>
</RoleBasedSecurity>
<TextChip
value="Pending"
value={$t('course.navItem.people.pending')}
className="text-xs bg-yellow-200 text-yellow-700 h-fit"
size="sm"
/>
@@ -248,7 +256,7 @@
<!-- second column -->
<StructuredListCell class="w-1/4 px-3">
<p class=" dark:text-white font-normal text-center text-base w-1/4">
{ROLE_LABEL[person.role_id]}
{$t(ROLE_LABEL[person.role_id])}
</p>
</StructuredListCell>
@@ -3,12 +3,13 @@
import PageNav from '$lib/components/PageNav/index.svelte';
import PageBody from '$lib/components/PageBody/index.svelte';
import Settings from '$lib/components/Course/components/Settings/index.svelte';
import { t } from '../../../../lib/utils/functions/translations';
export let data;
</script>
<CourseContainer bind:courseId={data.courseId}>
<PageNav title="Settings" />
<PageNav title={$t('course.navItem.settings.heading')} />
<PageBody>
<Settings />
</PageBody>
@@ -25,6 +25,7 @@
} from '$lib/utils/services/notification/notification';
import { currentOrg, currentOrgDomain } from '$lib/utils/store/org';
import { browser } from '$app/environment';
import { t } from '$lib/utils/functions/translations.js';
type items = {
id: number;
@@ -62,27 +63,27 @@
let openExercise = false;
const submissionStatus: { [key: number]: string } = {
1: 'Submitted',
2: 'In Progress',
3: 'Graded'
1: $t('course.navItem.submissions.submission_status.1'),
2: $t('course.navItem.submissions.submission_status.2'),
3: $t('course.navItem.submissions.submission_status.3')
};
let sections: sectionType[] = [
{
id: 1,
title: 'Submitted',
title: $t('course.navItem.submissions.submission_status.1'),
value: 0,
items: []
},
{
id: 2,
title: 'In Progress',
title: $t('course.navItem.submissions.submission_status.2'),
value: 0,
items: []
},
{
id: 3,
title: 'Graded',
title: $t('course.navItem.submissions.submission_status.3'),
value: 10,
items: []
}
@@ -251,7 +252,7 @@
total: totalPoints
}).then((res) => console.log('Updated submission', res));
snackbar.success('Grading saved and student notified');
snackbar.success('snackbar.submissions.success.grading');
await Promise.all(updates);
}
@@ -337,7 +338,7 @@
<RoleBasedSecurity allowedRoles={[1, 2]}>
<CourseContainer bind:courseId={data.courseId}>
<PageNav title="Submitted Exercises" />
<PageNav title={$t('course.navItem.submissions.title')} />
<PageBody width="w-full max-w-6xl md:w-11/12 overflow-x-auto">
<div class="flex items-center w-full">