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
@@ -14,6 +14,7 @@
import { group } from '$lib/components/Course/store';
import { profile } from '$lib/utils/store/user';
import { calDateDiff } from '$lib/utils/functions/date';
import { t } from '$lib/utils/functions/translations';
export let handleClose = () => {};
@@ -49,7 +50,7 @@
...comments,
{
comment: comment,
name: 'You',
name: $t('course.navItem.lessons.comments.you'),
avatar: $profile.avatar_url,
commentAt: new Date()
}
@@ -165,7 +166,7 @@
commentAt: lessonComment.created_at,
name:
lessonComment.groupmember.id === groupmember?.id
? 'You'
? $t('course.navItem.lessons.comments.you')
: lessonComment.groupmember.profile.fullname
};
});
@@ -191,7 +192,7 @@
$: !isFetched && fetchComments($group.people, $lesson.id);
</script>
<PageNav title="Comments" overidableStyle="padding: 0 10px">
<PageNav title={$t('course.navItem.lessons.comments.title')} overidableStyle="padding: 0 10px">
<div slot="widget">
<CloseButton onClick={handleClose} />
</div>
@@ -224,7 +225,7 @@
<div class="footer">
<TextField
placeholder="Say something"
placeholder={$t('course.navItem.lessons.comments.placeholder')}
bind:value={comment}
onKeyDown={handleKeyDown}
isDisabled={isSaving}
@@ -8,6 +8,7 @@
import TextField from '$lib/components/Form/TextField.svelte';
import { Add, TrashCan } from 'carbon-icons-svelte';
import IconButton from '$lib/components/IconButton/index.svelte';
import { t } from '$lib/utils/functions/translations';
export let title = 'Poll';
export let isSaving = false;
@@ -20,7 +21,7 @@
question: '',
expiration: new Date().toDateString(),
isPublic: false,
status: 'draft',
status: $t('course.navItem.lessons.poll.draft'),
author: {
id: $profile.id || '',
username: $profile.username || '',
@@ -70,14 +71,14 @@
</div>
<div class="p-3">
<TextField
label="Question"
label={$t('course.navItem.lessons.poll.question')}
className="w-full mb-3"
bind:value={poll.question}
placeholder="Poll question"
placeholder={$t('course.navItem.lessons.poll.poll_question')}
isRequired={true}
/>
<DateTime
label="Expiration"
label={$t('course.navItem.lessons.poll.expiration')}
className="w-full mb-3"
bind:value={poll.expiration}
isRequired={true}
@@ -85,7 +86,7 @@
<div>
<p class="dark:text-white p-0 m-0 mb-1 text-md flex items-center gap-2">
Options
{$t('course.navItem.lessons.poll.options')}
<IconButton onClick={handleAddOptions} contained={true} size="small">
<Add size={16} />
@@ -99,7 +100,7 @@
label=""
className="w-full"
bind:value={option.label}
placeholder="Option Label"
placeholder={$t('course.navItem.lessons.poll.option_label')}
isRequired={true}
/>
<IconButton onClick={handleRemoveOptions(index)} contained={true} size="small">
@@ -111,11 +112,15 @@
<div class="w-full flex justify-center gap-2 mb-3">
<PrimaryButton
label="Create poll"
label={$t('course.navItem.lessons.poll.create_poll')}
onClick={finishPoll}
isLoading={isSaving}
isDisabled={isCreateDisabled}
/>
<PrimaryButton label="Cancel" variant={VARIANTS.OUTLINED} onClick={onCancel} />
<PrimaryButton
label={$t('course.navItem.lessons.poll.cancel')}
variant={VARIANTS.OUTLINED}
onClick={onCancel}
/>
</div>
</div>
@@ -1,10 +1,12 @@
<script lang="ts">
<script script lang="ts">
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import Label from './Label.svelte';
import Avatar from './Avatar.svelte';
import type { PollType } from '../types';
import { updatePollStatus } from '../service';
import { t } from '$lib/utils/functions/translations';
import { Dashboard } from 'carbon-icons-svelte';
export let poll: PollType;
export let onSelect: (a: string) => void;
@@ -62,7 +64,7 @@
{`<`}
</button>
<div class="flex-grow text-center">
<h3 class="dark:text-white font-bold">Voted</h3>
<h3 class="dark:text-white font-bold">{$t('course.navItem.lessons.poll.voted')}</h3>
</div>
</div>
{#each selectedOptionToView.selectedBy as user}
@@ -81,13 +83,16 @@
{poll.question}
</h3>
<p class="dark:text-white text-sm italic">
Created by <span class="text-primary-500">@{poll.author.username}</span>
{$t('course.navItem.lessons.poll.created_by')}
<span class="text-primary-500">@{poll.author.username}</span>
</p>
</div>
{#if isAuthor}
<div class="flex mb-3 justify-end items-center">
<label class="mr-2 font-bold" for="user-select">Status:</label>
<label class="mr-2 font-bold" for="user-select"
>{$t('course.navItem.lessons.poll.status')}:</label
>
<select
name="users"
@@ -95,8 +100,8 @@
class="dark:text-white dark:bg-black"
bind:value={poll.status}
>
<option value="draft">Draft</option>
<option value="published">Publish</option>
<option value="draft">{$t('course.navItem.lessons.poll.draft')}</option>
<option value="published">{$t('course.navItem.lessons.poll.publish')}</option>
</select>
</div>
{/if}
@@ -105,20 +110,20 @@
{#if viewResult}
<div class="flex items-center justify-between">
<div class="mr-5">
<Label>Expiration</Label>
<Label>{$t('course.navItem.lessons.poll.expiration')}</Label>
<p class="dark:text-white text-sm text-gray-600 mb-4">
{poll.expiration || 'Today'}
{poll.expiration || $t('dashboard.today')}
</p>
</div>
<div class="flex flex-col items-center">
<Label>Total Votes</Label>
<Label>{$t('course.navItem.lessons.poll.total_votes')}</Label>
<p class="dark:text-white text-sm text-gray-600 mb-4">
{totalVotes}
</p>
</div>
</div>
<Label>Responses</Label>
<Label>{$t('course.navItem.lessons.poll.responses')}</Label>
{#each poll.options as option}
<div class="border-b p-5">
<p class="dark:text-white">
@@ -176,11 +181,11 @@
{/each}
<div class="flex items-center">
<p class="dark:text-white text-sm text-gray-600">
Expires: {poll.expiration || 'Today'}
{$t('course.navItem.lessons.poll.expires')}: {poll.expiration || $t('dashboard.today')}
</p>
<span class="mx-2">|</span>
<p class="dark:text-white text-sm text-gray-600">
Voted: {totalVotes}
{$t('course.navItem.lessons.poll.voted')}: {totalVotes}
</p>
</div>
{/if}
@@ -188,10 +193,16 @@
{#if isAuthor}
<div class="w-full flex items-center mt-3">
<PrimaryButton
label={viewResult ? 'Back' : 'Result'}
label={viewResult
? $t('course.navItem.lessons.poll.back')
: $t('course.navItem.lessons.poll.result')}
onClick={() => (viewResult = !viewResult)}
/>
<PrimaryButton label="Delete" variant={VARIANTS.TEXT_DANGER} onClick={handlePollDelete} />
<PrimaryButton
label={$t('course.navItem.lessons.poll.delete')}
variant={VARIANTS.TEXT_DANGER}
onClick={handlePollDelete}
/>
</div>
{/if}
{/if}
@@ -20,6 +20,7 @@
RealtimePostgresChangesPayload,
PostgrestSingleResponse
} from '@supabase/supabase-js';
import { t } from '$lib/utils/functions/translations';
export let handleClose = () => {};
@@ -62,7 +63,7 @@
if (error || !data) {
console.log(error);
snackbar.error('An error occurred while creating poll');
snackbar.error('snackbar.poll.error.creating_poll');
return;
}
@@ -78,7 +79,7 @@
if (appsPollOptionsError || !appsPollOptionsData) {
console.log({ appsPollOptionsError });
snackbar.error('An error occurred while creating poll');
snackbar.error('snackbar.poll.error.creating_poll');
return;
}
@@ -229,12 +230,12 @@
);
tabs = [
{
label: 'Active polls',
label: $t('course.navItem.lessons.polls.active_polls'),
value: 0,
number: activePolls.length
},
{
label: 'Expired polls',
label: $t('course.navItem.lessons.polls.expired_polls'),
value: 1,
number: expiredPolls.length
}
@@ -242,7 +243,11 @@
}
</script>
<PageNav title="Poll" overidableStyle="padding: 0 10px" paddingClass="w-full">
<PageNav
title={$t('course.navItem.lessons.polls.title')}
overidableStyle="padding: 0 10px"
paddingClass="w-full"
>
<div slot="widget">
<CloseButton onClick={handleClose} />
</div>
@@ -260,7 +265,7 @@
<Tabs {tabs} bind:selectedTab onCreate={() => (openCreatePollForm = !openCreatePollForm)} />
{#if isLoading}
Loading...
{$t('course.navItem.lessons.polls.loading')}...
{:else}
{#each selectedTab === tabs[0].value ? activePolls : expiredPolls as poll}
<Poll
@@ -273,7 +278,7 @@
<div
class="bg-gray-100 dark:bg-neutral-800 border rounded-md h-60 flex items-center justify-center"
>
<h2 class="text-xl font-bold">No polls to display</h2>
<h2 class="text-xl font-bold">{$t('course.navItem.lessons.polls.no_polls')}</h2>
</div>
{/each}
{/if}
@@ -45,7 +45,7 @@ export const updatePollStatus = async (pollId: string, status: PollType['status'
if (error) {
console.log(error);
snackbar.error('An error occurred while updating poll status');
snackbar.error('snackbar.poll.error.updating_poll');
return;
}
};
@@ -64,7 +64,7 @@ export const togglePollSubmission = async (
});
if (error) {
console.log(error);
snackbar.error('An error occurred while submitting poll');
snackbar.error('snackbar.poll.error.submitting_poll');
return;
}
} else {
@@ -76,7 +76,7 @@ export const togglePollSubmission = async (
if (error) {
console.log(error);
snackbar.error('An error occurred while submitting poll');
snackbar.error('snackbar.poll.error.submitting_poll');
return;
}
}
@@ -101,7 +101,7 @@ export function handleVote(pollId: string, groupmemberId: string, author: PollTy
if (poll.id === pollId) {
// Prevent user from voting on a poll that the status is not published
if (poll.status !== 'published') {
snackbar.info('Poll is not published yet');
snackbar.info('snackbar.poll.info.not_published');
return poll;
}
@@ -109,7 +109,7 @@ export function handleVote(pollId: string, groupmemberId: string, author: PollTy
if (
poll.options.some((option) => isOptionSelectedByCurrentUser(option, groupmemberId))
) {
snackbar.info('You can only vote once');
snackbar.info('snackbar.poll.info.vote_once');
return poll;
}