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
@@ -5,14 +5,14 @@ export const ROLE = {
|
||||
};
|
||||
|
||||
export const ROLE_LABEL = {
|
||||
[ROLE.ADMIN]: 'Admin',
|
||||
[ROLE.TUTOR]: 'Tutor',
|
||||
[ROLE.STUDENT]: 'Student'
|
||||
[ROLE.ADMIN]: 'course.navItem.people.roles.admin',
|
||||
[ROLE.TUTOR]: 'course.navItem.people.roles.tutor',
|
||||
[ROLE.STUDENT]: 'course.navItem.people.roles.student'
|
||||
};
|
||||
|
||||
export const ROLES = [
|
||||
{
|
||||
label: 'Filter',
|
||||
label: 'course.navItem.people.roles.filter',
|
||||
value: 'all'
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { LOCALE } from '../types';
|
||||
|
||||
export const LANGUAGE = {
|
||||
[LOCALE.EN]: 'English',
|
||||
[LOCALE.HI]: 'Hindi',
|
||||
[LOCALE.FR]: 'French',
|
||||
[LOCALE.PT]: 'Portuguese',
|
||||
[LOCALE.DE]: 'German',
|
||||
[LOCALE.VI]: 'Vietnamese',
|
||||
[LOCALE.RU]: 'Russian',
|
||||
[LOCALE.ES]: 'Spanish'
|
||||
};
|
||||
|
||||
export const LANGUAGES = Object.keys(LANGUAGE).map((lang) => ({
|
||||
id: lang,
|
||||
text: LANGUAGE[lang]
|
||||
}));
|
||||
@@ -10,5 +10,5 @@ export const calDateDiff = (date: string | number | Date): string => {
|
||||
export const getGreeting = () => {
|
||||
const date = new Date();
|
||||
const hours = date.getHours();
|
||||
return hours < 12 ? 'Good Morning' : hours < 18 ? 'Good Afternoon' : 'Good Evening';
|
||||
return hours < 12 ? 'dashboard.morning_heading' : hours < 18 ? 'dashboard.afternoon_heading' : 'dashboard.evening_heading';
|
||||
};
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import i18n, { type Config } from 'sveltekit-i18n';
|
||||
import { LOCALE } from '$lib/utils/types';
|
||||
|
||||
interface Params {
|
||||
dateValue: number;
|
||||
value: number;
|
||||
download: number;
|
||||
award: number;
|
||||
val: Date;
|
||||
}
|
||||
|
||||
const config: Config<Partial<Params>> = {
|
||||
initLocale: 'en',
|
||||
// parser: parser(),
|
||||
loaders: [
|
||||
{
|
||||
locale: 'en',
|
||||
key: '',
|
||||
loader: async () => (await import('../translations/en.json')).default
|
||||
},
|
||||
{
|
||||
locale: 'hi',
|
||||
key: '',
|
||||
loader: async () => (await import('../translations/hi.json')).default
|
||||
},
|
||||
{
|
||||
locale: 'fr',
|
||||
key: '',
|
||||
loader: async () => (await import('../translations/fr.json')).default
|
||||
},
|
||||
{
|
||||
locale: 'pt',
|
||||
key: '',
|
||||
loader: async () => (await import('../translations/pt.json')).default
|
||||
},
|
||||
{
|
||||
locale: 'de',
|
||||
key: '',
|
||||
loader: async () => (await import('../translations/de.json')).default
|
||||
},
|
||||
{
|
||||
locale: 'vi',
|
||||
key: '',
|
||||
loader: async () => (await import('../translations/vi.json')).default
|
||||
},
|
||||
{
|
||||
locale: 'ru',
|
||||
key: '',
|
||||
loader: async () => (await import('../translations/ru.json')).default
|
||||
},
|
||||
{
|
||||
locale: 'es',
|
||||
key: '',
|
||||
loader: async () => (await import('../translations/es.json')).default
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const { t, loading, locales, locale, initialized, translations, loadTranslations } =
|
||||
new i18n(config);
|
||||
|
||||
export const selectedLocale = writable<string>('en');
|
||||
|
||||
// Translations logs
|
||||
loading.subscribe(async ($loading) => {
|
||||
if ($loading) {
|
||||
console.log('Loading translations...');
|
||||
|
||||
await loading.toPromise();
|
||||
}
|
||||
});
|
||||
|
||||
export async function handleLocaleChange(newLocale) {
|
||||
if (!newLocale) {
|
||||
return;
|
||||
}
|
||||
|
||||
locale.set(newLocale);
|
||||
|
||||
await fetch('/api/i18n', {
|
||||
body: JSON.stringify({ locale: newLocale }),
|
||||
method: 'POST'
|
||||
});
|
||||
}
|
||||
|
||||
export function lessonFallbackNote(
|
||||
note: string,
|
||||
translation: Record<LOCALE, string>,
|
||||
locale: LOCALE
|
||||
) {
|
||||
if (!translation) {
|
||||
return note;
|
||||
}
|
||||
|
||||
const content = translation[locale];
|
||||
|
||||
// if locale is english and no translated content for english but note exists
|
||||
if (locale === LOCALE.EN && !content && note?.length) {
|
||||
return note;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
@@ -158,7 +158,7 @@ export function fetchLesson(lessonId: Lesson['id']) {
|
||||
return supabase
|
||||
.from('lesson')
|
||||
.select(
|
||||
`id, note, videos, slide_url, call_url, totalExercises:exercise(count), totalComments:lesson_comment(count), lesson_completion(id, profile_id, is_complete)`
|
||||
`id, note, videos, slide_url, call_url, totalExercises:exercise(count), totalComments:lesson_comment(count), lesson_completion(id, profile_id, is_complete), lesson_language(id, content, locale)`
|
||||
)
|
||||
.eq('id', lessonId)
|
||||
.single();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type { User } from '@supabase/supabase-js';
|
||||
import { LOCALE } from '../types';
|
||||
|
||||
interface UserStore {
|
||||
openAuthModal: boolean;
|
||||
@@ -21,6 +22,7 @@ interface ProfileStore {
|
||||
goal: string;
|
||||
source: string;
|
||||
telegram_chat_id: number | null;
|
||||
locale: LOCALE;
|
||||
is_email_verified: boolean;
|
||||
verified_at: string;
|
||||
}
|
||||
@@ -44,6 +46,7 @@ export const defaultProfileState: ProfileStore = {
|
||||
goal: '',
|
||||
source: '',
|
||||
telegram_chat_id: null,
|
||||
locale: LOCALE.EN,
|
||||
is_email_verified: false,
|
||||
verified_at: ''
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,17 @@
|
||||
export * from './config';
|
||||
export * from './dashboard';
|
||||
|
||||
export enum LOCALE {
|
||||
EN = 'en',
|
||||
HI = 'hi',
|
||||
FR = 'fr',
|
||||
PT = 'pt',
|
||||
DE = 'de',
|
||||
VI = 'vi',
|
||||
RU = 'ru',
|
||||
ES = 'es'
|
||||
}
|
||||
|
||||
//===============Custom Type===============
|
||||
export interface GroupPerson {
|
||||
assigned_student_id: number | null;
|
||||
@@ -248,7 +259,9 @@ export interface LessonPage {
|
||||
id?: string | null;
|
||||
totalExercises: number;
|
||||
totalComments: number;
|
||||
isSaving: Boolean;
|
||||
locale: LOCALE;
|
||||
isSaving: boolean;
|
||||
isFetching: boolean;
|
||||
materials: {
|
||||
note: string;
|
||||
slide_url: string;
|
||||
|
||||
Reference in New Issue
Block a user