feat(dashboard): add view course site actions and publish flow (#690)

Add View course site and Publish course options to org course context menus.
Replace the course header View as student button with View Course Site and
a companion overflow menu that includes View as student plus list actions.
Show a publish prompt modal for unpublished courses and support #publish
deep links in course settings.
This commit is contained in:
Rotimi Best
2026-06-25 22:39:30 +01:00
committed by GitHub
parent 6d594b4296
commit 1305e2e454
17 changed files with 449 additions and 164 deletions
@@ -1,16 +1,8 @@
<script lang="ts"> <script lang="ts">
import * as DropdownMenu from '@cio/ui/base/dropdown-menu'; import * as DropdownMenu from '@cio/ui/base/dropdown-menu';
import EllipsisVerticalIcon from '@lucide/svelte/icons/ellipsis-vertical'; import EllipsisVerticalIcon from '@lucide/svelte/icons/ellipsis-vertical';
import ExternalLinkIcon from '@lucide/svelte/icons/external-link';
import CopyIcon from '@lucide/svelte/icons/copy';
import { copyCourseModal, deleteCourseModal } from '$features/course/utils/store';
import { copyPublicCoursePageUrl, openCoursePreview } from '$features/course/utils/course-preview';
import { currentOrgDomain } from '$lib/utils/store/org';
import { IconButton } from '@cio/ui/custom/icon-button'; import { IconButton } from '@cio/ui/custom/icon-button';
import { t } from '$lib/utils/functions/translations'; import CourseContextMenuContent from './course-context-menu-content.svelte';
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
interface Props { interface Props {
id: string; id: string;
@@ -34,43 +26,6 @@
}: Props = $props(); }: Props = $props();
const showPublicCourseLinks = $derived(isPublished && courseType === 'PUBLIC' && slug.trim().length > 0); const showPublicCourseLinks = $derived(isPublished && courseType === 'PUBLIC' && slug.trim().length > 0);
function redirect(url: string) {
goto(resolve(url, {}));
}
function handleShareCourse() {
redirect(`/courses/${id}/settings#share`);
}
function handleInvite() {
redirect(`/courses/${id}/people?add=true`);
}
function handleDeleteCourse() {
$deleteCourseModal.open = true;
$deleteCourseModal.id = id;
$deleteCourseModal.title = title;
}
function handleCloneCourse() {
$copyCourseModal.open = true;
$copyCourseModal.id = id;
$copyCourseModal.title = title;
$copyCourseModal.description = description;
}
function handleOpenPublicCourse() {
openCoursePreview({
courseId: id,
courseSlug: slug,
currentOrgDomain: $currentOrgDomain
});
}
async function handleCopyCourseUrl() {
await copyPublicCoursePageUrl(slug, $currentOrgDomain);
}
</script> </script>
{#if lmsPublicQuickOnly ? showPublicCourseLinks : true} {#if lmsPublicQuickOnly ? showPublicCourseLinks : true}
@@ -84,41 +39,7 @@
</IconButton> </IconButton>
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DropdownMenu.Content align="end"> <DropdownMenu.Content align="end">
{#if lmsPublicQuickOnly} <CourseContextMenuContent {id} {title} {description} {isPublished} {courseType} {slug} {lmsPublicQuickOnly} />
<DropdownMenu.Item onclick={handleOpenPublicCourse}>
<ExternalLinkIcon size={16} class="mr-2" />
{$t('courses.course_card.context_menu.open_public_course')}
</DropdownMenu.Item>
<DropdownMenu.Item onclick={() => void handleCopyCourseUrl()}>
<CopyIcon size={16} class="mr-2" />
{$t('courses.course_card.context_menu.copy_course_url')}
</DropdownMenu.Item>
{:else}
{#if showPublicCourseLinks}
<DropdownMenu.Item onclick={handleOpenPublicCourse}>
<ExternalLinkIcon size={16} class="mr-2" />
{$t('courses.course_card.context_menu.open_public_course')}
</DropdownMenu.Item>
<DropdownMenu.Item onclick={() => void handleCopyCourseUrl()}>
<CopyIcon size={16} class="mr-2" />
{$t('courses.course_card.context_menu.copy_course_url')}
</DropdownMenu.Item>
<DropdownMenu.Separator />
{/if}
<DropdownMenu.Item onclick={handleCloneCourse}>
{$t('courses.course_card.context_menu.clone')}
</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleShareCourse}>
{$t('courses.course_card.context_menu.share')}
</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleInvite}>
{$t('courses.course_card.context_menu.invite')}
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item class="text-red-600" onclick={handleDeleteCourse}>
{$t('courses.course_card.context_menu.delete')}
</DropdownMenu.Item>
{/if}
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Root> </DropdownMenu.Root>
{/if} {/if}
@@ -0,0 +1,155 @@
<script lang="ts">
import * as DropdownMenu from '@cio/ui/base/dropdown-menu';
import ExternalLinkIcon from '@lucide/svelte/icons/external-link';
import CopyIcon from '@lucide/svelte/icons/copy';
import EyeIcon from '@lucide/svelte/icons/eye';
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
import { copyCourseModal, deleteCourseModal } from '$features/course/utils/store';
import { copyPublicCoursePageUrl, openCoursePreview } from '$features/course/utils/course-preview';
import { currentOrgDomain } from '$lib/utils/store/org';
import { t } from '$lib/utils/functions/translations';
interface Props {
id: string;
title: string;
description: string;
isPublished?: boolean;
courseType?: string | null;
slug?: string;
/** Compact menu for LMS course cards */
lmsPublicQuickOnly?: boolean;
/** Include "View as student" (course header menu) */
includeViewAsStudent?: boolean;
onViewAsStudent?: () => void;
/** List view includes an explicit Open action */
includeOpen?: boolean;
/** Hide org management actions (clone, share, invite, delete) */
hideOrgActions?: boolean;
}
let {
id,
title,
description,
isPublished = false,
courseType = null,
slug = '',
lmsPublicQuickOnly = false,
includeViewAsStudent = false,
onViewAsStudent,
includeOpen = false,
hideOrgActions = false
}: Props = $props();
const showPublicCourseLinks = $derived(isPublished && courseType === 'PUBLIC' && slug.trim().length > 0);
function redirect(url: string) {
goto(resolve(url, {}));
}
function handleShareCourse() {
redirect(`/courses/${id}/settings#share`);
}
function handleInvite() {
redirect(`/courses/${id}/people?add=true`);
}
function handlePublishCourse() {
redirect(`/courses/${id}/settings#publish`);
}
function handleDeleteCourse() {
$deleteCourseModal.open = true;
$deleteCourseModal.id = id;
$deleteCourseModal.title = title;
}
function handleCloneCourse() {
$copyCourseModal.open = true;
$copyCourseModal.id = id;
$copyCourseModal.title = title;
$copyCourseModal.description = description;
}
function handleOpenCourse() {
redirect(`/courses/${id}`);
}
function handleViewCourseSite() {
openCoursePreview({
courseId: id,
courseSlug: slug,
currentOrgDomain: $currentOrgDomain
});
}
async function handleCopyCourseUrl() {
await copyPublicCoursePageUrl(slug, $currentOrgDomain);
}
</script>
{#if lmsPublicQuickOnly}
<DropdownMenu.Item onclick={handleViewCourseSite}>
<ExternalLinkIcon size={16} class="mr-2" />
{$t('courses.course_card.context_menu.view_course_site')}
</DropdownMenu.Item>
<DropdownMenu.Item onclick={() => void handleCopyCourseUrl()}>
<CopyIcon size={16} class="mr-2" />
{$t('courses.course_card.context_menu.copy_course_url')}
</DropdownMenu.Item>
{:else}
{#if includeViewAsStudent}
<DropdownMenu.Item onclick={() => onViewAsStudent?.()}>
<EyeIcon size={16} class="mr-2" />
{$t('course.header.view_as_student')}
</DropdownMenu.Item>
<DropdownMenu.Separator />
{/if}
{#if isPublished}
<DropdownMenu.Item onclick={handleViewCourseSite}>
<ExternalLinkIcon size={16} class="mr-2" />
{$t('courses.course_card.context_menu.view_course_site')}
</DropdownMenu.Item>
{#if showPublicCourseLinks}
<DropdownMenu.Item onclick={() => void handleCopyCourseUrl()}>
<CopyIcon size={16} class="mr-2" />
{$t('courses.course_card.context_menu.copy_course_url')}
</DropdownMenu.Item>
{/if}
{#if !hideOrgActions || includeOpen}
<DropdownMenu.Separator />
{/if}
{:else}
<DropdownMenu.Item onclick={handlePublishCourse}>
{$t('courses.course_card.context_menu.publish_course')}
</DropdownMenu.Item>
{#if !hideOrgActions || includeOpen}
<DropdownMenu.Separator />
{/if}
{/if}
{#if includeOpen}
<DropdownMenu.Item onclick={handleOpenCourse}>
{$t('courses.course_card.context_menu.open')}
</DropdownMenu.Item>
{/if}
{#if !hideOrgActions}
<DropdownMenu.Item onclick={handleCloneCourse}>
{$t('courses.course_card.context_menu.clone')}
</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleShareCourse}>
{$t('courses.course_card.context_menu.share')}
</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleInvite}>
{$t('courses.course_card.context_menu.invite')}
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item class="text-red-600" onclick={handleDeleteCourse}>
{$t('courses.course_card.context_menu.delete')}
</DropdownMenu.Item>
{/if}
{/if}
@@ -1,7 +1,10 @@
<script lang="ts"> <script lang="ts">
import { Separator } from '@cio/ui/base/separator'; import { Separator } from '@cio/ui/base/separator';
import * as Sidebar from '@cio/ui/base/sidebar'; import * as Sidebar from '@cio/ui/base/sidebar';
import EyeIcon from '@lucide/svelte/icons/eye'; import * as ButtonGroup from '@cio/ui/base/button-group';
import * as DropdownMenu from '@cio/ui/base/dropdown-menu';
import ExternalLinkIcon from '@lucide/svelte/icons/external-link';
import EllipsisVerticalIcon from '@lucide/svelte/icons/ellipsis-vertical';
import { Button } from '@cio/ui/base/button'; import { Button } from '@cio/ui/base/button';
import { Waves } from '@cio/ui/custom/animation'; import { Waves } from '@cio/ui/custom/animation';
import { page } from '$app/state'; import { page } from '$app/state';
@@ -12,23 +15,46 @@
import { courseApi } from '$features/course/api'; import { courseApi } from '$features/course/api';
import { getActiveCourseNavKey } from '$features/course/utils/functions'; import { getActiveCourseNavKey } from '$features/course/utils/functions';
import { toggleAiAssistant } from '$features/ai-assistant/utils/store'; import { toggleAiAssistant } from '$features/ai-assistant/utils/store';
import { openCoursePreview } from '$features/course/utils/course-preview';
import { t } from '$lib/utils/functions/translations'; import { t } from '$lib/utils/functions/translations';
import CoursePublishBadge from './course-publish-badge.svelte'; import CoursePublishBadge from './course-publish-badge.svelte';
import CoursePublicBadge from './course-public-badge.svelte'; import CoursePublicBadge from './course-public-badge.svelte';
import CourseContextMenuContent from './course-context-menu-content.svelte';
import ViewAsStudentModal from './view-as-student-modal.svelte'; import ViewAsStudentModal from './view-as-student-modal.svelte';
import ViewCourseSiteUnpublishedModal from './view-course-site-unpublished-modal.svelte';
const siteName = $derived($currentOrg.siteName); const siteName = $derived($currentOrg.siteName);
const showCoursePublishBadge = $derived(!$isStudentExperience); const showCoursePublishBadge = $derived(!$isStudentExperience);
const isPublicCourse = $derived(courseApi.course?.type === 'PUBLIC'); const isPublicCourse = $derived(courseApi.course?.type === 'PUBLIC');
const activeNavKey = $derived(getActiveCourseNavKey(page.url.pathname, courseApi.course?.id ?? '')); const activeNavKey = $derived(getActiveCourseNavKey(page.url.pathname, courseApi.course?.id ?? ''));
const isPublished = $derived(courseApi.course?.isPublished ?? false);
let viewAsStudentOpen = $state(false); let viewAsStudentOpen = $state(false);
let viewCourseSiteUnpublishedOpen = $state(false);
$effect(() => { $effect(() => {
if (!siteName) return; if (!siteName) return;
setupProgressApi.fetchSetupProgress(siteName); setupProgressApi.fetchSetupProgress(siteName);
}); });
function handleViewCourseSite() {
const course = courseApi.course;
if (!course?.id) {
return;
}
if (!isPublished) {
viewCourseSiteUnpublishedOpen = true;
return;
}
openCoursePreview({
courseId: course.id,
courseSlug: course.slug,
currentOrgDomain: $currentOrgDomain
});
}
</script> </script>
<header <header
@@ -52,7 +78,7 @@
{/if} {/if}
{#if showCoursePublishBadge} {#if showCoursePublishBadge}
<CoursePublishBadge isPublished={courseApi.course?.isPublished ?? false} /> <CoursePublishBadge {isPublished} />
{/if} {/if}
</div> </div>
</div> </div>
@@ -78,16 +104,47 @@
</Button> </Button>
{#if !$isStudentExperience} {#if !$isStudentExperience}
<ButtonGroup.Root>
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
onclick={() => (viewAsStudentOpen = true)} onclick={handleViewCourseSite}
disabled={!courseApi.course?.id} disabled={!courseApi.course?.id}
aria-label={$t('course.header.view_as_student')} aria-label={$t('course.header.view_course_site')}
> >
<EyeIcon size={14} /> <ExternalLinkIcon size={14} />
<span class="hidden sm:inline">{$t('course.header.view_as_student')}</span> <span class="hidden sm:inline">{$t('course.header.view_course_site')}</span>
</Button> </Button>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
{#snippet child({ props })}
<Button
{...props}
variant="outline"
size="sm"
aria-label={$t('courses.course_card.actions_menu_aria')}
disabled={!courseApi.course?.id}
>
<EllipsisVerticalIcon size={14} />
</Button>
{/snippet}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
{#if courseApi.course}
<CourseContextMenuContent
id={courseApi.course.id}
title={courseApi.course.title}
description={courseApi.course.description}
isPublished={courseApi.course.isPublished ?? false}
courseType={courseApi.course.type}
slug={courseApi.course.slug ?? ''}
includeViewAsStudent={true}
onViewAsStudent={() => (viewAsStudentOpen = true)}
/>
{/if}
</DropdownMenu.Content>
</DropdownMenu.Root>
</ButtonGroup.Root>
{/if} {/if}
</div> </div>
</header> </header>
@@ -98,3 +155,5 @@
courseSlug={courseApi.course?.slug} courseSlug={courseApi.course?.slug}
currentOrgDomain={$currentOrgDomain} currentOrgDomain={$currentOrgDomain}
/> />
<ViewCourseSiteUnpublishedModal bind:open={viewCourseSiteUnpublishedOpen} currentOrgDomain={$currentOrgDomain} />
@@ -10,14 +10,10 @@
import { ContentType } from '@cio/utils/constants/content'; import { ContentType } from '@cio/utils/constants/content';
import { Image } from '$features/ui'; import { Image } from '$features/ui';
import { t } from '$lib/utils/functions/translations'; import { t } from '$lib/utils/functions/translations';
import { copyCourseModal, deleteCourseModal } from '$features/course/utils/store';
import { copyPublicCoursePageUrl, openCoursePreview } from '$features/course/utils/course-preview';
import { currentOrgDomain } from '$lib/utils/store/org';
import { buildCoursePlaceholderAvatarUrl } from '$features/course/utils/course-list-row-utils'; import { buildCoursePlaceholderAvatarUrl } from '$features/course/utils/course-list-row-utils';
import CourseContentIcon from './course-content-icon.svelte'; import CourseContentIcon from './course-content-icon.svelte';
import CoursePublicBadge from './course-public-badge.svelte'; import CoursePublicBadge from './course-public-badge.svelte';
import ExternalLinkIcon from '@lucide/svelte/icons/external-link'; import CourseContextMenuContent from './course-context-menu-content.svelte';
import CopyIcon from '@lucide/svelte/icons/copy';
interface Tag { interface Tag {
id: string; id: string;
@@ -133,30 +129,6 @@
} }
goto(resolve(`/courses/${id}`, {})); goto(resolve(`/courses/${id}`, {}));
} }
function handleOpen(e: MouseEvent) {
e.stopPropagation();
goto(resolve(`/courses/${id}`, {}));
}
function handleClone(e: MouseEvent) {
e.stopPropagation();
$copyCourseModal.open = true;
$copyCourseModal.id = id;
$copyCourseModal.title = title;
$copyCourseModal.description = description;
}
function handleDelete(e: MouseEvent) {
e.stopPropagation();
$deleteCourseModal.open = true;
$deleteCourseModal.id = id;
$deleteCourseModal.title = title;
}
async function handleCopyCourseUrl() {
await copyPublicCoursePageUrl(slug, $currentOrgDomain);
}
</script> </script>
<ResourceListRow.Root <ResourceListRow.Root
@@ -317,37 +289,27 @@
{/snippet} {/snippet}
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DropdownMenu.Content align="end"> <DropdownMenu.Content align="end">
{#if showPublicCourseLinks} {#if isLMS}
<DropdownMenu.Item <CourseContextMenuContent
onclick={() => {id}
openCoursePreview({ {title}
courseId: id, {description}
courseSlug: slug, {isPublished}
currentOrgDomain: $currentOrgDomain courseType={type}
})} {slug}
> lmsPublicQuickOnly={true}
<ExternalLinkIcon class="mr-2 size-4" /> />
{$t('courses.course_card.context_menu.open_public_course')} {:else}
</DropdownMenu.Item> <CourseContextMenuContent
<DropdownMenu.Item onclick={() => void handleCopyCourseUrl()}> {id}
<CopyIcon class="mr-2 size-4" /> {title}
{$t('courses.course_card.context_menu.copy_course_url')} {description}
</DropdownMenu.Item> {isPublished}
{/if} courseType={type}
{#if !isLMS} {slug}
{#if showPublicCourseLinks} includeOpen={true}
<DropdownMenu.Separator /> hideOrgActions={false}
{/if} />
<DropdownMenu.Item onclick={handleOpen}>
{$t('courses.course_card.context_menu.open')}
</DropdownMenu.Item>
<DropdownMenu.Item onclick={handleClone}>
{$t('courses.course_card.context_menu.clone')}
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item variant="destructive" onclick={handleDelete}>
{$t('courses.course_card.context_menu.delete')}
</DropdownMenu.Item>
{/if} {/if}
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Root> </DropdownMenu.Root>
@@ -0,0 +1,66 @@
<script lang="ts">
import { Button } from '@cio/ui/base/button';
import * as Dialog from '@cio/ui/base/dialog';
import { courseApi } from '$features/course/api';
import { publishCourse } from '$features/course/utils/publish-course';
import { openCoursePreview } from '$features/course/utils/course-preview';
import { t } from '$lib/utils/functions/translations';
interface Props {
open?: boolean;
currentOrgDomain?: string;
}
let { open = $bindable(false), currentOrgDomain = '' }: Props = $props();
let isPublishing = $state(false);
async function handlePublishCourse() {
const course = courseApi.course;
if (!course?.id) {
return;
}
isPublishing = true;
const published = await publishCourse(course);
isPublishing = false;
if (!published) {
return;
}
open = false;
openCoursePreview({
courseId: course.id,
courseSlug: courseApi.course?.slug,
currentOrgDomain
});
}
</script>
<Dialog.Root
bind:open
onOpenChange={(isOpen) => {
if (!isOpen && !isPublishing) {
open = false;
}
}}
>
<Dialog.Content class="w-[calc(100%-2rem)] max-w-md! p-4">
<Dialog.Header>
<Dialog.Title>{$t('course.view_course_site.unpublished_title')}</Dialog.Title>
<Dialog.Description>{$t('course.view_course_site.unpublished_description')}</Dialog.Description>
</Dialog.Header>
<Dialog.Footer>
<Button variant="outline" onclick={() => (open = false)} disabled={isPublishing}>
{$t('course.view_course_site.cancel')}
</Button>
<Button variant="default" onclick={handlePublishCourse} loading={isPublishing} disabled={!courseApi.course?.id}>
{$t('course.view_course_site.publish_course')}
</Button>
</Dialog.Footer>
</Dialog.Content>
</Dialog.Root>
@@ -794,7 +794,7 @@
<Field.Separator /> <Field.Separator />
<Field.Set> <Field.Set id="publish">
<Field.Legend>{$t('course.navItem.settings.publish')}</Field.Legend> <Field.Legend>{$t('course.navItem.settings.publish')}</Field.Legend>
<Field.Description>{$t('course.navItem.settings.determines')}</Field.Description> <Field.Description>{$t('course.navItem.settings.determines')}</Field.Description>
<Field.Field orientation="horizontal"> <Field.Field orientation="horizontal">
@@ -0,0 +1,32 @@
import { generateSlug } from '@cio/utils/functions';
import { isObject } from '$lib/utils/functions/isObject';
import { courseApi } from '$features/course/api';
import type { Course } from '$features/course/utils/types';
export async function publishCourse(course: Course): Promise<boolean> {
if (!course?.id) {
return false;
}
let slug = course.slug?.trim() ? course.slug : undefined;
if (!slug) {
slug = generateSlug(course.title, { appendTimestamp: true });
}
const metadataPayload = {
...(isObject(course.metadata) ? course.metadata : {}),
allowNewStudent: true
};
const result = await courseApi.update(
course.id,
{
isPublished: true,
slug,
metadata: metadataPayload
},
{ showSuccessToast: true }
);
return !!result;
}
@@ -2234,7 +2234,14 @@
"header": { "header": {
"preview": "Preview course", "preview": "Preview course",
"preview_missing_slug": "Generate and save a course slug before previewing.", "preview_missing_slug": "Generate and save a course slug before previewing.",
"view_as_student": "Se som studerende" "view_as_student": "Se som studerende",
"view_course_site": "Se kursusside"
},
"view_course_site": {
"unpublished_title": "Udgiv dit kursus",
"unpublished_description": "Du bør udgive dette kursus, før du kan se sitet",
"cancel": "Annuller",
"publish_course": "Udgiv kursus"
}, },
"sidebar": { "sidebar": {
"back_to_course": "Tilbage til kurset", "back_to_course": "Tilbage til kurset",
@@ -2420,6 +2427,8 @@
"delete": "Slet", "delete": "Slet",
"open": "Åbn", "open": "Åbn",
"open_public_course": "Åbn offentligt kursus", "open_public_course": "Åbn offentligt kursus",
"view_course_site": "Se kursusside",
"publish_course": "Udgiv kursus",
"copy_course_url": "Kopiér kursus-URL" "copy_course_url": "Kopiér kursus-URL"
}, },
"due_date": "Forfalder", "due_date": "Forfalder",
@@ -2234,7 +2234,14 @@
"header": { "header": {
"preview": "Preview course", "preview": "Preview course",
"preview_missing_slug": "Generate and save a course slug before previewing.", "preview_missing_slug": "Generate and save a course slug before previewing.",
"view_as_student": "Als Student anzeigen" "view_as_student": "Als Student anzeigen",
"view_course_site": "Kursseite anzeigen"
},
"view_course_site": {
"unpublished_title": "Kurs veröffentlichen",
"unpublished_description": "Sie sollten diesen Kurs veröffentlichen, bevor Sie die Seite ansehen können",
"cancel": "Abbrechen",
"publish_course": "Kurs veröffentlichen"
}, },
"sidebar": { "sidebar": {
"back_to_course": "Zurück zum Kurs", "back_to_course": "Zurück zum Kurs",
@@ -2420,6 +2427,8 @@
"delete": "Löschen", "delete": "Löschen",
"open": "Offen", "open": "Offen",
"open_public_course": "Öffentlichen Kurs öffnen", "open_public_course": "Öffentlichen Kurs öffnen",
"view_course_site": "Kursseite anzeigen",
"publish_course": "Kurs veröffentlichen",
"copy_course_url": "Kurs-URL kopieren" "copy_course_url": "Kurs-URL kopieren"
}, },
"due_date": "Fällig", "due_date": "Fällig",
@@ -1970,6 +1970,8 @@
"invite": "Invite", "invite": "Invite",
"delete": "Delete", "delete": "Delete",
"open_public_course": "Open public course", "open_public_course": "Open public course",
"view_course_site": "View course site",
"publish_course": "Publish course",
"copy_course_url": "Copy course URL" "copy_course_url": "Copy course URL"
}, },
"error_message": "An error occurred.", "error_message": "An error occurred.",
@@ -2147,7 +2149,14 @@
"header": { "header": {
"preview": "Preview course", "preview": "Preview course",
"preview_missing_slug": "Generate and save a course slug before previewing.", "preview_missing_slug": "Generate and save a course slug before previewing.",
"view_as_student": "View as student" "view_as_student": "View as student",
"view_course_site": "View Course Site"
},
"view_course_site": {
"unpublished_title": "Publish your course",
"unpublished_description": "You should publish this course before you can see the site",
"cancel": "Cancel",
"publish_course": "Publish Course"
}, },
"view_as_student": { "view_as_student": {
"title": "This will redirect you to the Student LMS.", "title": "This will redirect you to the Student LMS.",
@@ -2234,7 +2234,14 @@
"header": { "header": {
"preview": "Preview course", "preview": "Preview course",
"preview_missing_slug": "Generate and save a course slug before previewing.", "preview_missing_slug": "Generate and save a course slug before previewing.",
"view_as_student": "Ver como estudiante" "view_as_student": "Ver como estudiante",
"view_course_site": "Ver sitio del curso"
},
"view_course_site": {
"unpublished_title": "Publica tu curso",
"unpublished_description": "Debes publicar este curso antes de poder ver el sitio",
"cancel": "Cancelar",
"publish_course": "Publicar curso"
}, },
"sidebar": { "sidebar": {
"back_to_course": "volver al curso", "back_to_course": "volver al curso",
@@ -2420,6 +2427,8 @@
"delete": "Eliminar", "delete": "Eliminar",
"open": "Abierto", "open": "Abierto",
"open_public_course": "Abrir curso público", "open_public_course": "Abrir curso público",
"view_course_site": "Ver sitio del curso",
"publish_course": "Publicar curso",
"copy_course_url": "Copiar URL del curso" "copy_course_url": "Copiar URL del curso"
}, },
"due_date": "debido", "due_date": "debido",
@@ -2234,7 +2234,14 @@
"header": { "header": {
"preview": "Preview course", "preview": "Preview course",
"preview_missing_slug": "Generate and save a course slug before previewing.", "preview_missing_slug": "Generate and save a course slug before previewing.",
"view_as_student": "Afficher en tant qu'étudiant" "view_as_student": "Afficher en tant qu'étudiant",
"view_course_site": "Voir le site du cours"
},
"view_course_site": {
"unpublished_title": "Publiez votre cours",
"unpublished_description": "Vous devez publier ce cours avant de pouvoir voir le site",
"cancel": "Annuler",
"publish_course": "Publier le cours"
}, },
"sidebar": { "sidebar": {
"back_to_course": "Retour au cours", "back_to_course": "Retour au cours",
@@ -2420,6 +2427,8 @@
"delete": "Supprimer", "delete": "Supprimer",
"open": "Ouvert", "open": "Ouvert",
"open_public_course": "Ouvrir le cours public", "open_public_course": "Ouvrir le cours public",
"view_course_site": "Voir le site du cours",
"publish_course": "Publier le cours",
"copy_course_url": "Copier l’URL du cours" "copy_course_url": "Copier l’URL du cours"
}, },
"due_date": "Due", "due_date": "Due",
@@ -2234,7 +2234,14 @@
"header": { "header": {
"preview": "Preview course", "preview": "Preview course",
"preview_missing_slug": "Generate and save a course slug before previewing.", "preview_missing_slug": "Generate and save a course slug before previewing.",
"view_as_student": "विद्यार्थी के रूप में देखें" "view_as_student": "विद्यार्थी के रूप में देखें",
"view_course_site": "कोर्स साइट देखें"
},
"view_course_site": {
"unpublished_title": "अपना कोर्स प्रकाशित करें",
"unpublished_description": "साइट देखने से पहले आपको इस कोर्स को प्रकाशित करना चाहिए",
"cancel": "रद्द करें",
"publish_course": "कोर्स प्रकाशित करें"
}, },
"sidebar": { "sidebar": {
"back_to_course": "पाठ्यक्रम पर वापस जाएँ", "back_to_course": "पाठ्यक्रम पर वापस जाएँ",
@@ -2420,6 +2427,8 @@
"delete": "हटाएँ", "delete": "हटाएँ",
"open": "खुला", "open": "खुला",
"open_public_course": "सार्वजनिक कोर्स खोलें", "open_public_course": "सार्वजनिक कोर्स खोलें",
"view_course_site": "कोर्स साइट देखें",
"publish_course": "कोर्स प्रकाशित करें",
"copy_course_url": "कोर्स URL कॉपी करें" "copy_course_url": "कोर्स URL कॉपी करें"
}, },
"due_date": "देय", "due_date": "देय",
@@ -2234,7 +2234,14 @@
"header": { "header": {
"preview": "Preview course", "preview": "Preview course",
"preview_missing_slug": "Generate and save a course slug before previewing.", "preview_missing_slug": "Generate and save a course slug before previewing.",
"view_as_student": "Wyświetl jako student" "view_as_student": "Wyświetl jako student",
"view_course_site": "Zobacz stronę kursu"
},
"view_course_site": {
"unpublished_title": "Opublikuj swój kurs",
"unpublished_description": "Powinieneś opublikować ten kurs, zanim zobaczysz stronę",
"cancel": "Anuluj",
"publish_course": "Opublikuj kurs"
}, },
"sidebar": { "sidebar": {
"back_to_course": "Powrót na kurs", "back_to_course": "Powrót na kurs",
@@ -2420,6 +2427,8 @@
"delete": "Usuń", "delete": "Usuń",
"open": "Otwórz", "open": "Otwórz",
"open_public_course": "Otwórz publiczny kurs", "open_public_course": "Otwórz publiczny kurs",
"view_course_site": "Zobacz stronę kursu",
"publish_course": "Opublikuj kurs",
"copy_course_url": "Kopiuj adres URL kursu" "copy_course_url": "Kopiuj adres URL kursu"
}, },
"due_date": "Termin", "due_date": "Termin",
@@ -2234,7 +2234,14 @@
"header": { "header": {
"preview": "Preview course", "preview": "Preview course",
"preview_missing_slug": "Generate and save a course slug before previewing.", "preview_missing_slug": "Generate and save a course slug before previewing.",
"view_as_student": "Ver como aluno" "view_as_student": "Ver como aluno",
"view_course_site": "Ver site do curso"
},
"view_course_site": {
"unpublished_title": "Publique seu curso",
"unpublished_description": "Você deve publicar este curso antes de poder ver o site",
"cancel": "Cancelar",
"publish_course": "Publicar curso"
}, },
"sidebar": { "sidebar": {
"back_to_course": "De volta ao curso", "back_to_course": "De volta ao curso",
@@ -2420,6 +2427,8 @@
"delete": "Excluir", "delete": "Excluir",
"open": "Abrir", "open": "Abrir",
"open_public_course": "Abrir curso público", "open_public_course": "Abrir curso público",
"view_course_site": "Ver site do curso",
"publish_course": "Publicar curso",
"copy_course_url": "Copiar URL do curso" "copy_course_url": "Copiar URL do curso"
}, },
"due_date": "Devido", "due_date": "Devido",
@@ -2234,7 +2234,14 @@
"header": { "header": {
"preview": "Preview course", "preview": "Preview course",
"preview_missing_slug": "Generate and save a course slug before previewing.", "preview_missing_slug": "Generate and save a course slug before previewing.",
"view_as_student": "Посмотреть как студент" "view_as_student": "Посмотреть как студент",
"view_course_site": "Посмотреть сайт курса"
},
"view_course_site": {
"unpublished_title": "Опубликуйте курс",
"unpublished_description": "Перед просмотром сайта необходимо опубликовать этот курс",
"cancel": "Отмена",
"publish_course": "Опубликовать курс"
}, },
"sidebar": { "sidebar": {
"back_to_course": "Вернуться к курсу", "back_to_course": "Вернуться к курсу",
@@ -2420,6 +2427,8 @@
"delete": "Удалить", "delete": "Удалить",
"open": "Открыть", "open": "Открыть",
"open_public_course": "Открыть публичный курс", "open_public_course": "Открыть публичный курс",
"view_course_site": "Посмотреть сайт курса",
"publish_course": "Опубликовать курс",
"copy_course_url": "Копировать URL курса" "copy_course_url": "Копировать URL курса"
}, },
"due_date": "Срок погашения", "due_date": "Срок погашения",
@@ -2234,7 +2234,14 @@
"header": { "header": {
"preview": "Preview course", "preview": "Preview course",
"preview_missing_slug": "Generate and save a course slug before previewing.", "preview_missing_slug": "Generate and save a course slug before previewing.",
"view_as_student": "Xem với tư cách sinh viên" "view_as_student": "Xem với tư cách sinh viên",
"view_course_site": "Xem trang khóa học"
},
"view_course_site": {
"unpublished_title": "Xuất bản khóa học của bạn",
"unpublished_description": "Bạn nên xuất bản khóa học này trước khi có thể xem trang web",
"cancel": "Hủy",
"publish_course": "Xuất bản khóa học"
}, },
"sidebar": { "sidebar": {
"back_to_course": "Quay lại khóa học", "back_to_course": "Quay lại khóa học",
@@ -2420,6 +2427,8 @@
"delete": "Xóa", "delete": "Xóa",
"open": "Mở", "open": "Mở",
"open_public_course": "Mở khóa học công khai", "open_public_course": "Mở khóa học công khai",
"view_course_site": "Xem trang khóa học",
"publish_course": "Xuất bản khóa học",
"copy_course_url": "Sao chép URL khóa học" "copy_course_url": "Sao chép URL khóa học"
}, },
"due_date": "Đến hạn", "due_date": "Đến hạn",