{
+ if (href === pathname && !pending) return;
+ setTarget(href);
+ setKind(options.kind ?? 'project');
+ startTransition(() => {
+ if (options.replace) router.replace(href, { scroll: options.scroll });
+ else router.push(href, { scroll: options.scroll });
+ });
+ },
+ }}>
+
+ {background}
+ {showDrawer ?
+ {pending && kind === 'project' ? : children}
+ : children}
+
+ {languagePending && }
+
);
}
-
-export function useDismissCaseStudy() {
- const dismiss = useContext(PortfolioRouteContext);
- if (!dismiss) throw new Error('useDismissCaseStudy must be used within PortfolioRouteShell');
- return dismiss;
-}
diff --git a/src/components/ProjectsSection.tsx b/src/components/ProjectsSection.tsx
index 0b3da8d..96240f2 100644
--- a/src/components/ProjectsSection.tsx
+++ b/src/components/ProjectsSection.tsx
@@ -3,7 +3,7 @@ import { formatMessage } from '@/content/contracts';
import React, { useCallback, useEffect, useRef, useState } from 'react';
-import Link from 'next/link';
+import { PortfolioLink as Link } from './PortfolioNavigation';
import { ArrowLeft, ArrowRight, ArrowUpRight } from 'lucide-react';
import { projectPath } from '@/content/contracts';
import type { PortfolioProps } from '@/server/content';
diff --git a/src/content/messages.en.json b/src/content/messages.en.json
index 16d9e45..3b6417e 100644
--- a/src/content/messages.en.json
+++ b/src/content/messages.en.json
@@ -95,5 +95,12 @@
"linkedin": "LinkedIn",
"github": "GitHub",
"telegram": "Telegram"
+ },
+ "loading": {
+ "page": "Loading portfolio…",
+ "project": "Loading case study…",
+ "language": "Changing language…",
+ "error": "This page could not be loaded.",
+ "retry": "Try again"
}
}
diff --git a/src/content/messages.fa.json b/src/content/messages.fa.json
index 5931e4f..7f66c1e 100644
--- a/src/content/messages.fa.json
+++ b/src/content/messages.fa.json
@@ -95,5 +95,12 @@
"linkedin": "لینکدین",
"github": "گیتهاب",
"telegram": "تلگرام"
+ },
+ "loading": {
+ "page": "در حال بارگذاری نمونهکارها…",
+ "project": "در حال بارگذاری مطالعه موردی…",
+ "language": "در حال تغییر زبان…",
+ "error": "بارگذاری این صفحه ممکن نشد.",
+ "retry": "تلاش دوباره"
}
}
diff --git a/src/server/public.ts b/src/server/public.ts
index be84513..809c65f 100644
--- a/src/server/public.ts
+++ b/src/server/public.ts
@@ -2,9 +2,23 @@ import 'server-only';
import { cache } from 'react';
import { connection } from 'next/server';
import { notFound } from 'next/navigation';
+import { eq } from 'drizzle-orm';
import { getDatabase } from './db';
+import { languages, siteSettings } from './schema';
import { ContentError, readContent, resolvePublicRoute } from './content';
+// Resolve only document attributes before streaming the portfolio content.
+export async function getPublicLanguage(locale?: string) {
+ await connection();
+ const { db } = getDatabase();
+ const settings = db.select().from(siteSettings).get()?.data;
+ if (!settings) throw new ContentError('Database is not initialized', 503);
+ const code = locale ?? settings.defaultLanguage;
+ const language = db.select().from(languages).where(eq(languages.code, code)).get()?.data;
+ if (!language?.enabled || locale === settings.defaultLanguage) notFound();
+ return language;
+}
+
export const readPublicSnapshot = cache(async () => {
await connection();
return readContent(getDatabase());
diff --git a/tests/project-case-studies.test.mjs b/tests/project-case-studies.test.mjs
index 779e383..3183d26 100644
--- a/tests/project-case-studies.test.mjs
+++ b/tests/project-case-studies.test.mjs
@@ -29,7 +29,7 @@ test('localized project routes use runtime content and metadata', async () => {
test('project cards separate case study navigation from live links', async () => {
const source = await readFile('src/components/ProjectsSection.tsx', 'utf8');
- assert.match(source, /import Link from ['"]next\/link['"]/);
+ assert.match(source, /PortfolioLink as Link/);
assert.match(source, /scroll=\{false\}/);
assert.match(source, /target="_blank"/);
assert.match(source, /rel="noopener noreferrer"/);