diff --git a/svelte-app/src/components/document/content/common/footer.svelte b/svelte-app/src/components/document/content/common/footer.svelte index c568dceea..48ad51dec 100644 --- a/svelte-app/src/components/document/content/common/footer.svelte +++ b/svelte-app/src/components/document/content/common/footer.svelte @@ -14,7 +14,7 @@ ← {$t('Read more')} diff --git a/svelte-app/src/components/layouts/page-transition.svelte b/svelte-app/src/components/layouts/page-transition.svelte index a558078d1..8d8a4d392 100644 --- a/svelte-app/src/components/layouts/page-transition.svelte +++ b/svelte-app/src/components/layouts/page-transition.svelte @@ -11,8 +11,8 @@ const { reduce_motion } = Settings; - const dist = 28, - duration = BASE_ANIMATION_DURATION / 1.25; + const dist = 26, + duration = BASE_ANIMATION_DURATION / 1.5; let navDir: 'forward' | 'backward' = 'forward'; diff --git a/svelte-app/src/lib/consts.ts b/svelte-app/src/lib/consts.ts index 3e5de38ed..06668c250 100644 --- a/svelte-app/src/lib/consts.ts +++ b/svelte-app/src/lib/consts.ts @@ -1,8 +1,8 @@ export const LIGHT_THEME = 'light'; export const DARK_THEME = 'dark'; -export const APP_LANGS = ['en', 'fr'] as const; -export const DEFAULT_APP_LANG = 'en'; +export const APP_LANGS = ['en', 'fr']; +export const DEFAULT_APP_LANG = APP_LANGS[0]; export const VALID_DOC_TYPES = ['post', 'project', 'about', 'config'] as const; diff --git a/svelte-app/src/lib/helpers/i18n.ts b/svelte-app/src/lib/helpers/i18n.ts index d1983e1f6..b12b899a1 100644 --- a/svelte-app/src/lib/helpers/i18n.ts +++ b/svelte-app/src/lib/helpers/i18n.ts @@ -1,4 +1,4 @@ -import { get, readable, writable } from 'svelte/store'; +import { derived, get, writable } from 'svelte/store'; import { page } from '$app/stores'; import EN from '$langs/en.json'; @@ -50,8 +50,12 @@ const getKey = (lang: string, key: T): string | undef } }; -const _translate = (key: string, params?: Record): string => { - const lang = get(currentLang) || DEFAULT_APP_LANG; +const _translate = ( + currentLang: string | undefined, + key: string, + params?: Record +): string => { + const lang = currentLang || DEFAULT_APP_LANG; // For any provided params, replace the corresponding placeholders if any // e.g. "Hello {name}" with { name: "World" } becomes "Hello World" @@ -65,25 +69,19 @@ const _translate = (key: string, params?: Record): string => { ); }; - const string = getKey(lang || DEFAULT_APP_LANG, key as keyof typeof EN); + const string = getKey(lang, key as keyof typeof EN); if (string) { return replaceParams(string); } else { - notFound(key, lang || DEFAULT_APP_LANG); + notFound(key, lang); return replaceParams(key); } }; -// eslint-disable-next-line func-call-spacing -const translate = readable<(key: string, params?: Record) => string>( - _translate, - (set) => { - const unsubscribe = currentLang.subscribe(() => { - set(_translate); - }); - return unsubscribe; - } +const translate = derived( + currentLang, + (val) => (key: string, params?: Record) => _translate(val, key, params) ); const addSearchParams = (path: string, params?: URLSearchParams): string => { @@ -92,7 +90,8 @@ const addSearchParams = (path: string, params?: URLSearchParams): string => { return search ? `${path}${path.includes('?') ? '&' : '?'}${search}` : path; }; -const _linkTo: LinkTo = ( +const _linkTo = ( + currentLang: string | undefined, path: string, paramsOrLang?: URLSearchParams | string, lang?: string @@ -109,7 +108,7 @@ const _linkTo: LinkTo = ( params = paramsOrLang; } - lang = lang || get(currentLang); + lang ||= currentLang || DEFAULT_APP_LANG; if ( !lang || @@ -135,8 +134,11 @@ interface LinkTo { (pathname: string, params: URLSearchParams, lang?: string): string; } -const linkTo = readable(_linkTo, (set) => - currentLang.subscribe(() => set(_linkTo)) +const linkTo = derived( + currentLang, + (val) => + ((path: string, paramsOrLang?: URLSearchParams | string, lang?: string) => + _linkTo(val, path, paramsOrLang, lang)) as LinkTo ); export { check, currentLang, isLocalized, linkTo, translate as t }; diff --git a/svelte-app/src/lib/helpers/navigation.ts b/svelte-app/src/lib/helpers/navigation.ts index a10535cf8..8318001eb 100644 --- a/svelte-app/src/lib/helpers/navigation.ts +++ b/svelte-app/src/lib/helpers/navigation.ts @@ -1,69 +1,60 @@ -import { derived, get } from 'svelte/store'; +import { derived } from 'svelte/store'; import { page } from '$app/stores'; -import { currentLang, isLocalized, t } from '$i18n'; -import { APP_ROUTES, BASE_PAGE_TITLE, ROUTE_ORDER } from '$lib/consts'; +import { isLocalized, t } from '$i18n'; +import { APP_LANGS, APP_ROUTES, BASE_PAGE_TITLE, ROUTE_ORDER } from '$lib/consts'; -let prevPath: string; - -const forward: string[] = []; -ROUTE_ORDER.forEach((route, index) => { - index !== ROUTE_ORDER.length - 1 && - ROUTE_ORDER.forEach((subRoute, i) => { - !(i <= index) && forward.push(`${route}-${subRoute}`); - }); -}); +const langRegex = new RegExp(`^/(${APP_LANGS.join('|')})`), + firstSegRegex = /^\/([^/]+)/, + subsequentSegRegex = /\//g; -export const pageTitle = derived([currentLang, page], (_v) => { - const basePathname = get(page)?.url?.pathname ?? '/', - pathname = `/${ - (get(isLocalized) ? basePathname.slice(3) : basePathname.slice(1)).split('/')[0] - }`; +const normalizePath = (path: string) => { + if (path.match(langRegex)) { + path = path.slice(3); + } - const route = APP_ROUTES.find((r) => r.path === pathname)?.name; + if (!path || path === '/') { + return 'index'; + } - return route?.length ? `${BASE_PAGE_TITLE} | ${get(t)(route)}` : BASE_PAGE_TITLE; -}); + const firstSegmentMatch = path.match(firstSegRegex); -export const onNav = (path: string): 'forward' | 'backward' => { - if (get(isLocalized) === true && path) { - path = `/${path.slice(3)}`; + if (!firstSegmentMatch) { + return ''; } - path = path.replace(/^\/{2,}/, '/').replace(/.+\/$/, ''); + const subsequentSegmentsCount = (path.match(subsequentSegRegex) || []).length - 1; + + return firstSegmentMatch[1] + '/*'.repeat(subsequentSegmentsCount); +}; + +const forward = new Set(); - if (!path) { - return 'forward'; +for (let i = 0; i < ROUTE_ORDER.length - 1; ++i) { + for (let j = i + 1; j < ROUTE_ORDER.length; ++j) { + forward.add(`${ROUTE_ORDER[i]}-${ROUTE_ORDER[j]}`); } +} + +let prevPath: string; + +export const onNav = (path: string) => { + const newPath = normalizePath(path), + oldPath = prevPath ? prevPath : newPath; + + prevPath = newPath; - const prev = prevPath ?? path; - prevPath = path; - - const toRoute = path.startsWith('/') - ? path - .slice(1) - .split('/') - .map((part, i) => (i === 0 ? part : '*')) - .join('/') - : path - .split('/') - .map((part, i) => (i === 0 ? part : '*')) - .join('/'); - const fromRoute = prev.startsWith('/') - ? prev - .slice(1) - .split('/') - .map((part, i) => (i === 0 ? part : '*')) - .join('/') - : prev - .split('/') - .map((part, i) => (i === 0 ? part : '*')) - .join('/'); - - const dirs = [ - fromRoute === '' ? 'index' : fromRoute, - toRoute === '' ? 'index' : toRoute - ].join('-'); - - return forward.includes(dirs) ? 'forward' : 'backward'; + return forward.has(`${oldPath}-${newPath}`) ? 'forward' : 'backward'; }; + +export const pageTitle = derived([isLocalized, t, page], (vals) => { + const basePathname = vals[2]?.url?.pathname ?? '/', + pathname = `/${ + (vals[0] ? basePathname.slice(4) : basePathname.slice(1)).split('/')[0] + }`, + route = APP_ROUTES.find((r) => r.path === pathname); + + return route?.name?.length + ? `${BASE_PAGE_TITLE} | ${vals[1](route.name)}` + : BASE_PAGE_TITLE; +}); diff --git a/svelte-app/src/routes/+error.svelte b/svelte-app/src/routes/+error.svelte index fdf380775..6b101b7f1 100644 --- a/svelte-app/src/routes/+error.svelte +++ b/svelte-app/src/routes/+error.svelte @@ -71,7 +71,7 @@ {#if showStack} + class="mt-4 whitespace-pre-wrap break-all rounded-md border border-dark/40 p-4 font-code text-sm dark:border-light/40"> {stack}
+ class="mt-4 whitespace-pre-wrap break-all rounded-md border border-dark/40 p-4 font-code text-sm dark:border-light/40"> {stack}