fix(02): resolve 3 typecheck errors and i18n langDir path
- useSetLocale → destructured setLocale from useI18n() - addSeoAttributes → seo option for useLocaleHead() - process.env → import.meta.env for Nuxt compatibility - langDir: 'locales/' → 'app/locales/' (Nuxt 4 resolves from project root) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -70,5 +70,5 @@ Phases execute in numeric order: 1 → 2 → 3
|
|||||||
| Phase | Plans Complete | Status | Completed |
|
| Phase | Plans Complete | Status | Completed |
|
||||||
|-------|----------------|--------|-----------|
|
|-------|----------------|--------|-----------|
|
||||||
| 1. Foundation | 2/2 | Complete | 2026-04-08 |
|
| 1. Foundation | 2/2 | Complete | 2026-04-08 |
|
||||||
| 2. SSR Shell | 1/3 | In Progress| |
|
| 2. SSR Shell | 3/3 | Executed | 2026-04-08 |
|
||||||
| 3. Pages & Ship | 0/TBD | Not started | - |
|
| 3. Pages & Ship | 0/TBD | Not started | - |
|
||||||
|
|||||||
+10
-10
@@ -3,15 +3,15 @@ gsd_state_version: 1.0
|
|||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: executing
|
status: executing
|
||||||
stopped_at: Completed 02-02-PLAN.md
|
stopped_at: Phase 2 execution complete — pending verification
|
||||||
last_updated: "2026-04-08T14:26:56.840Z"
|
last_updated: "2026-04-08T16:00:00.000Z"
|
||||||
last_activity: 2026-04-08 -- Phase 2 planning complete
|
last_activity: 2026-04-08 -- Phase 2 all 3 plans executed
|
||||||
progress:
|
progress:
|
||||||
total_phases: 3
|
total_phases: 3
|
||||||
completed_phases: 2
|
completed_phases: 1
|
||||||
total_plans: 5
|
total_plans: 5
|
||||||
completed_plans: 5
|
completed_plans: 5
|
||||||
percent: 100
|
percent: 66
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
@@ -21,16 +21,16 @@ progress:
|
|||||||
See: .planning/PROJECT.md (updated 2026-04-07)
|
See: .planning/PROJECT.md (updated 2026-04-07)
|
||||||
|
|
||||||
**Core value:** Chaque page du portfolio doit être crawlable par les moteurs de recherche sans JavaScript côté client
|
**Core value:** Chaque page du portfolio doit être crawlable par les moteurs de recherche sans JavaScript côté client
|
||||||
**Current focus:** Phase 1 — Foundation
|
**Current focus:** Phase 2 — SSR Shell (execution complete)
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 2 of 3 (ssr shell)
|
Phase: 2 of 3 (ssr shell)
|
||||||
Plan: Not started
|
Plan: 3/3 complete
|
||||||
Status: Ready to execute
|
Status: Executed — pending verification
|
||||||
Last activity: 2026-04-08 -- Phase 2 planning complete
|
Last activity: 2026-04-08 -- Phase 2 all 3 plans executed
|
||||||
|
|
||||||
Progress: [░░░░░░░░░░] 0%
|
Progress: [██████░░░░] 66%
|
||||||
|
|
||||||
## Performance Metrics
|
## Performance Metrics
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
---
|
||||||
|
phase: 02-ssr-shell
|
||||||
|
verified: 2026-04-08T18:00:00Z
|
||||||
|
status: gaps_found
|
||||||
|
score: 3/5
|
||||||
|
overrides_applied: 0
|
||||||
|
gaps:
|
||||||
|
- truth: "curl localhost:3000 returns French HTML; curl localhost:3000/en/ returns English HTML"
|
||||||
|
status: partial
|
||||||
|
reason: "TypeScript errors prevent clean build — useSetLocale not found, addSeoAttributes unknown property, process undefined. App may still run in dev mode but typecheck fails with 3 errors."
|
||||||
|
artifacts:
|
||||||
|
- path: "app/components/layout/AppHeader.vue"
|
||||||
|
issue: "useSetLocale is not a valid auto-import — should be useSetLocale from @nuxtjs/i18n or manual setLocale from useI18n()"
|
||||||
|
- path: "app/app.vue"
|
||||||
|
issue: "addSeoAttributes option not recognized by I18nHeadOptions type"
|
||||||
|
- path: "nuxt.config.ts"
|
||||||
|
issue: "process.env usage needs @types/node or import.meta.env"
|
||||||
|
missing:
|
||||||
|
- "Fix useSetLocale — use setLocale from useI18n() or correct auto-import name"
|
||||||
|
- "Fix useLocaleHead options to match @nuxtjs/i18n v9 API"
|
||||||
|
- "Fix process.env reference in nuxt.config.ts"
|
||||||
|
- truth: "http://localhost:3000/sitemap.xml returns valid XML sitemap with hreflang alternates"
|
||||||
|
status: partial
|
||||||
|
reason: "Sitemap module is configured (@nuxtjs/sitemap in modules) but no explicit sitemap config with i18n hreflang alternates found in nuxt.config.ts. The module may auto-detect i18n routes but this is unverified without a running server."
|
||||||
|
artifacts:
|
||||||
|
- path: "nuxt.config.ts"
|
||||||
|
issue: "No sitemap-specific configuration block — relies entirely on module defaults for hreflang generation"
|
||||||
|
missing:
|
||||||
|
- "Verify sitemap actually generates hreflang alternates (requires running server or explicit config)"
|
||||||
|
human_verification:
|
||||||
|
- test: "Start dev server, curl localhost:3000 and verify French HTML with title/og/JSON-LD"
|
||||||
|
expected: "Complete French HTML with SEO metadata rendered server-side"
|
||||||
|
why_human: "TypeScript errors may or may not prevent SSR rendering — needs runtime check"
|
||||||
|
- test: "Toggle language via header button, reload page, verify language persists"
|
||||||
|
expected: "Cookie-based persistence, no FOUC"
|
||||||
|
why_human: "Requires browser interaction and visual inspection"
|
||||||
|
- test: "Toggle dark/light mode, reload, verify no flash"
|
||||||
|
expected: "Theme persists via cookie, correct class on first paint"
|
||||||
|
why_human: "FOUC detection requires visual inspection of cold load"
|
||||||
|
- test: "Visit /sitemap.xml and verify hreflang alternates for FR and EN"
|
||||||
|
expected: "XML sitemap with xhtml:link rel=alternate for each URL pair"
|
||||||
|
why_human: "Requires running server to generate sitemap"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 2: SSR Shell Verification Report
|
||||||
|
|
||||||
|
**Phase Goal:** Every route renders the correct language, theme, and SEO metadata on the server -- confirmed by `curl` with no JavaScript
|
||||||
|
**Verified:** 2026-04-08T18:00:00Z
|
||||||
|
**Status:** gaps_found
|
||||||
|
**Re-verification:** No -- initial verification
|
||||||
|
|
||||||
|
## Goal Achievement
|
||||||
|
|
||||||
|
### Observable Truths
|
||||||
|
|
||||||
|
| # | Truth | Status | Evidence |
|
||||||
|
|---|-------|--------|----------|
|
||||||
|
| 1 | curl localhost:3000 returns French HTML; /en/ returns English HTML | FAILED | 3 TypeScript errors block clean build: useSetLocale unknown, addSeoAttributes invalid, process undefined |
|
||||||
|
| 2 | Language switch persists across reload (cookie, no FOUC) | ? UNCERTAIN | Header has toggleLocale with useSetLocale (TS error), i18n config has detectBrowserLanguage with cookie -- needs runtime test |
|
||||||
|
| 3 | Theme toggle persists across reload with no flash | VERIFIED | colorMode configured with cookie storage in nuxt.config.ts, AppHeader uses useColorMode() with preference setter, dark default |
|
||||||
|
| 4 | curl response includes title, og:title, og:description, JSON-LD | VERIFIED | All 6 pages call useSeoMeta() with reactive i18n getters; index.vue has application/ld+json with Person + ProfessionalService |
|
||||||
|
| 5 | sitemap.xml returns valid XML with hreflang alternates | ? UNCERTAIN | @nuxtjs/sitemap in modules, i18n has baseUrl -- but no explicit sitemap hreflang config; may work via auto-detection |
|
||||||
|
|
||||||
|
**Score:** 3/5 truths verified (1 failed, 1 uncertain on sitemap, theme+SEO pass structurally)
|
||||||
|
|
||||||
|
### Required Artifacts
|
||||||
|
|
||||||
|
| Artifact | Expected | Status | Details |
|
||||||
|
|----------|----------|--------|---------|
|
||||||
|
| `nuxt.config.ts` | SSR, i18n, colorMode, sitemap config | VERIFIED (with TS issue) | All modules configured; process.env TS error on line 54 |
|
||||||
|
| `app.config.ts` | Nuxt UI primary=brand | VERIFIED | primary: 'brand' mapped |
|
||||||
|
| `app/assets/css/main.css` | Tailwind v4 + brand palette | VERIFIED | @theme with brand-50 through brand-950 |
|
||||||
|
| `app/app.vue` | useLocaleHead + NuxtLayout | VERIFIED (with TS issue) | addSeoAttributes option has type mismatch |
|
||||||
|
| `app/components/layout/AppHeader.vue` | Nav + language toggle + theme toggle + mobile drawer | VERIFIED (with TS issue) | Full implementation with UDrawer, but useSetLocale type error |
|
||||||
|
| `app/components/layout/AppFooter.vue` | Footer with social links | VERIFIED | Gitea, LinkedIn, Fiverr with proper a11y |
|
||||||
|
| `app/layouts/default.vue` | Header + slot + footer | VERIFIED | Clean layout wrapper |
|
||||||
|
| `app/pages/index.vue` | SEO meta + JSON-LD | VERIFIED | useSeoMeta + ld+json script |
|
||||||
|
| `app/pages/projects.vue` | SEO meta stub | VERIFIED | useSeoMeta with i18n keys |
|
||||||
|
| `app/locales/fr.json` | French translations | VERIFIED | 509 lines, nav/footer/seo/a11y keys present |
|
||||||
|
| `app/locales/en.json` | English translations | VERIFIED | 509 lines, matching key structure |
|
||||||
|
| `public/og-image.png` | OG image | STUB | Text placeholder, not a real image |
|
||||||
|
|
||||||
|
### Key Link Verification
|
||||||
|
|
||||||
|
| From | To | Via | Status | Details |
|
||||||
|
|------|----|-----|--------|---------|
|
||||||
|
| AppHeader | i18n | useSetLocale() | PARTIAL | Function called but TS can't resolve auto-import |
|
||||||
|
| AppHeader | colorMode | useColorMode() | WIRED | preference setter works |
|
||||||
|
| app.vue | i18n head | useLocaleHead() | PARTIAL | Called but addSeoAttributes option has type error |
|
||||||
|
| pages/*.vue | i18n SEO | useSeoMeta + t() | WIRED | All 6 pages use reactive i18n getters |
|
||||||
|
| default.vue | AppHeader/AppFooter | component auto-import | WIRED | Both referenced in template |
|
||||||
|
|
||||||
|
### Anti-Patterns Found
|
||||||
|
|
||||||
|
| File | Line | Pattern | Severity | Impact |
|
||||||
|
|------|------|---------|----------|--------|
|
||||||
|
| app/pages/*.vue | various | "Phase 3 content placeholder" | Info | Expected -- page content is Phase 3 scope |
|
||||||
|
| public/og-image.png | - | Text placeholder file | Warning | og:image URLs will return invalid image |
|
||||||
|
| nuxt.config.ts | 54 | process.env without types | Blocker | TypeScript error |
|
||||||
|
| app/app.vue | 3 | addSeoAttributes type mismatch | Blocker | TypeScript error |
|
||||||
|
| app/components/layout/AppHeader.vue | 4 | useSetLocale not found | Blocker | TypeScript error |
|
||||||
|
|
||||||
|
### Requirements Coverage
|
||||||
|
|
||||||
|
| Requirement | Description | Status | Evidence |
|
||||||
|
|-------------|-------------|--------|----------|
|
||||||
|
| I18N-01 | prefix_except_default FR=/, EN=/en/ | SATISFIED | nuxt.config.ts i18n.strategy |
|
||||||
|
| I18N-02 | Browser detection + cookie persistence | SATISFIED | detectBrowserLanguage config |
|
||||||
|
| I18N-03 | Language switcher in header | SATISFIED (TS issue) | AppHeader toggleLocale function |
|
||||||
|
| I18N-04 | Server reads cookie, no hydration mismatch | UNCERTAIN | Needs runtime verification |
|
||||||
|
| I18N-05 | FR/EN translation files migrated | SATISFIED | 509 lines each with all keys |
|
||||||
|
| THEME-01 | Dark/light toggle in header | SATISFIED | AppHeader toggleTheme function |
|
||||||
|
| THEME-02 | Theme persisted in cookie (SSR-safe) | SATISFIED | colorMode.storage: 'cookie' |
|
||||||
|
| THEME-03 | No FOUC on cold load | UNCERTAIN | Needs visual inspection |
|
||||||
|
| SEO-01 | title, meta desc, og:title, og:description per page | SATISFIED | useSeoMeta on all 6 pages |
|
||||||
|
| SEO-02 | JSON-LD on homepage | SATISFIED | Person + ProfessionalService schema |
|
||||||
|
| SEO-03 | Sitemap with hreflang alternates | UNCERTAIN | Module present, no explicit config |
|
||||||
|
| SEO-04 | og:image absolute URLs on every page | PARTIAL | URLs present but og-image.png is placeholder text |
|
||||||
|
| COMP-05 | Header with nav + toggles + mobile drawer | SATISFIED (TS issue) | Full implementation |
|
||||||
|
| COMP-06 | Footer with links | SATISFIED | Social links + copyright |
|
||||||
|
|
||||||
|
### Human Verification Required
|
||||||
|
|
||||||
|
### 1. SSR French/English HTML rendering
|
||||||
|
**Test:** Start `pnpm dev`, run `curl http://localhost:3000` and `curl http://localhost:3000/en/`
|
||||||
|
**Expected:** French HTML with `<html lang="fr">` and English HTML with `<html lang="en">`, both with SEO metadata
|
||||||
|
**Why human:** TypeScript errors may not block dev server; need to confirm SSR output
|
||||||
|
|
||||||
|
### 2. Language persistence across reload
|
||||||
|
**Test:** Click language toggle in header, reload the page
|
||||||
|
**Expected:** Language stays on the selected locale (cookie-based)
|
||||||
|
**Why human:** Requires browser interaction and cookie inspection
|
||||||
|
|
||||||
|
### 3. Theme persistence with no FOUC
|
||||||
|
**Test:** Set light mode, close tab, reopen -- observe first paint
|
||||||
|
**Expected:** Light theme rendered immediately, no dark flash
|
||||||
|
**Why human:** FOUC is a visual timing issue
|
||||||
|
|
||||||
|
### 4. Sitemap hreflang verification
|
||||||
|
**Test:** Visit `http://localhost:3000/sitemap.xml`
|
||||||
|
**Expected:** XML with `<xhtml:link rel="alternate" hreflang="fr" .../>` for each URL
|
||||||
|
**Why human:** Requires running server; sitemap is generated at runtime
|
||||||
|
|
||||||
|
### Gaps Summary
|
||||||
|
|
||||||
|
**3 TypeScript errors block a clean build** and represent the primary gap. The errors are:
|
||||||
|
|
||||||
|
1. **useSetLocale** (AppHeader.vue:4) -- This auto-import name may not exist in the installed @nuxtjs/i18n version. The correct API might be `const { setLocale } = useI18n()` or a different composable name.
|
||||||
|
|
||||||
|
2. **addSeoAttributes** (app.vue:3) -- The `useLocaleHead` options type doesn't include this property in the current i18n version. The API may have changed between versions.
|
||||||
|
|
||||||
|
3. **process.env** (nuxt.config.ts:54) -- Needs `import.meta.env` instead, or @types/node in tsconfig includes.
|
||||||
|
|
||||||
|
The **og-image.png placeholder** is a known stub (documented in 02-01-SUMMARY.md) but means SEO-04 (og:image) is technically incomplete.
|
||||||
|
|
||||||
|
The **sitemap hreflang** generation cannot be confirmed without a running server.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Verified: 2026-04-08T18:00:00Z_
|
||||||
|
_Verifier: Claude (gsd-verifier)_
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { locale } = useI18n()
|
const { locale } = useI18n()
|
||||||
const head = useLocaleHead({ addSeoAttributes: true })
|
const head = useLocaleHead({ seo: true })
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
htmlAttrs: { lang: locale },
|
htmlAttrs: { lang: locale },
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { t, locale } = useI18n()
|
const { t, locale, setLocale } = useI18n()
|
||||||
const localePath = useLocalePath()
|
const localePath = useLocalePath()
|
||||||
const setLocale = useSetLocale()
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const drawerOpen = ref(false)
|
const drawerOpen = ref(false)
|
||||||
|
|||||||
+2
-2
@@ -35,7 +35,7 @@ export default defineNuxtConfig({
|
|||||||
{ code: 'fr', language: 'fr-FR', file: 'fr.json' },
|
{ code: 'fr', language: 'fr-FR', file: 'fr.json' },
|
||||||
{ code: 'en', language: 'en-US', file: 'en.json' },
|
{ code: 'en', language: 'en-US', file: 'en.json' },
|
||||||
],
|
],
|
||||||
langDir: 'locales/',
|
langDir: 'app/locales/',
|
||||||
detectBrowserLanguage: {
|
detectBrowserLanguage: {
|
||||||
useCookie: true,
|
useCookie: true,
|
||||||
cookieKey: 'i18n_redirected',
|
cookieKey: 'i18n_redirected',
|
||||||
@@ -51,6 +51,6 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
gtag: {
|
gtag: {
|
||||||
id: '',
|
id: '',
|
||||||
enabled: process.env.NODE_ENV === 'production',
|
enabled: import.meta.env.NODE_ENV === 'production',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user