implement ssg config for blog posts in project ,

This commit is contained in:
mahdiarghyani
2025-11-10 18:09:18 +03:30
parent d2333d3db2
commit 713bb83981
37 changed files with 5505 additions and 297 deletions
+595
View File
@@ -0,0 +1,595 @@
# i18n Routing Fixes Design Document
## Overview
This document outlines the technical design for fixing i18n routing and hydration issues in the Nuxt 4 portfolio application. The issues include Vue Router warnings during language switching, hydration mismatches in the Footer component, and accessibility warnings in the language switcher.
### Current Issues
1. **Vue Router Warnings**: When switching languages, Vue Router cannot find blog post routes with language prefixes
2. **Hydration Mismatch**: Footer component causes hydration errors due to colorMode access during SSR
3. **ARIA Warning**: Language switcher has aria-hidden on focusable elements
4. **Route Resolution**: Blog routes with `/en/` or `/fa/` prefixes are not properly resolved
### Design Goals
1. **Fix Route Resolution**: Ensure all blog routes work correctly with language prefixes
2. **Eliminate Hydration Errors**: Make Footer component SSR-safe
3. **Improve Accessibility**: Fix ARIA warnings in language switcher
4. **Maintain User Experience**: Keep smooth language switching without breaking functionality
## Architecture
### Current i18n Configuration
```typescript
// nuxt.config.ts
i18n: {
defaultLocale: 'en',
strategy: 'prefix_except_default', // ← This is the issue!
locales: [
{ code: 'en', language: 'en-US', name: 'English', dir: 'ltr', file: 'en.json' },
{ code: 'fa', language: 'fa-IR', name: 'فارسی', dir: 'rtl', file: 'fa.json' },
],
// ...
}
```
**Problem**: The `prefix_except_default` strategy means:
- English routes: `/blog/post-slug` (no prefix)
- Persian routes: `/fa/blog/post-slug` (with prefix)
This causes issues because:
1. Content is organized as `content/en/blog/` and `content/fa/blog/`
2. When switching languages, the router looks for `/en/blog/` routes that don't exist
3. Blog navigation uses `localePath()` which generates inconsistent paths
### Root Cause Analysis
#### Issue 1: Route Strategy Mismatch
**Current Behavior**:
- Content structure: `content/{locale}/blog/`
- Route strategy: `prefix_except_default` (English has no prefix)
- Blog queries: `queryContent('${locale}/blog')`
**Problem**: When on `/blog/post` (English) and switching to Persian, the app tries to navigate to `/fa/blog/post`, but the content query still uses the old locale path.
**Solution**: Change strategy to `prefix` so all routes have locale prefixes consistently.
#### Issue 2: Hydration Mismatch in Footer
**Current Code**:
```vue
<script setup lang="ts">
const colorMode = useColorMode()
const logoSrc = computed(() => {
if (colorMode.unknown) {
return '/favicon/android-chrome-192x192.png'
}
return colorMode.value === 'dark'
? '/favicon/android-chrome-192x192-dark.png'
: '/favicon/android-chrome-192x192.png'
})
</script>
```
**Problem**:
- Server renders with `colorMode.unknown = true` (default)
- Client hydrates with actual colorMode from localStorage
- HTML mismatch causes hydration error
**Solution**: Use `ClientOnly` for colorMode-dependent content or defer rendering until mounted.
#### Issue 3: ARIA Warning in Language Switcher
**Current Code**:
```vue
<template>
<USelect
v-model="model"
:items="items"
aria-label="Language selector"
:ui="{ value: 'sr-only' }"
>
<!-- ... -->
</USelect>
</template>
```
**Problem**: The `sr-only` class likely adds `aria-hidden="true"` to focusable elements, which is invalid.
**Solution**: Remove `sr-only` from focusable elements and use proper ARIA labels instead.
## Components and Interfaces
### 1. i18n Configuration Update
**File**: `nuxt.config.ts`
**Changes**:
```typescript
i18n: {
defaultLocale: 'en',
strategy: 'prefix', // ← Change from 'prefix_except_default'
locales: [
{ code: 'en', language: 'en-US', name: 'English', dir: 'ltr', file: 'en.json' },
{ code: 'fa', language: 'fa-IR', name: 'فارسی', dir: 'rtl', file: 'fa.json' },
],
langDir: 'locales',
detectBrowserLanguage: {
useCookie: true,
cookieKey: 'i18n_redirected',
alwaysRedirect: false,
redirectOn: 'root'
},
vueI18n: '~/i18n.config.ts'
}
```
**Impact**:
- All routes will have locale prefix: `/en/`, `/fa/`
- Root `/` will redirect to `/en/` (default locale)
- Consistent URL structure across all pages
- Blog routes: `/en/blog/post` and `/fa/blog/post`
**Migration Notes**:
- Update all internal links to use `localePath()`
- Update sitemap generation
- Update prerender routes
- Test all navigation flows
### 2. Footer Component Fix
**File**: `app/components/common/FooterCopyright.vue`
**Current Implementation**:
```vue
<template>
<footer class="py-10">
<UContainer>
<div class="flex flex-col items-center gap-4">
<NuxtImg :src="logoSrc" alt="Ali Arghyani logo" />
<!-- ... -->
</div>
</UContainer>
</footer>
</template>
<script setup lang="ts">
const colorMode = useColorMode()
const logoSrc = computed(() => {
if (colorMode.unknown) {
return '/favicon/android-chrome-192x192.png'
}
return colorMode.value === 'dark'
? '/favicon/android-chrome-192x192-dark.png'
: '/favicon/android-chrome-192x192.png'
})
</script>
```
**Solution 1: Use ClientOnly (Recommended)**:
```vue
<template>
<footer class="py-10">
<UContainer>
<div class="flex flex-col items-center gap-4">
<ClientOnly>
<NuxtImg :src="logoSrc" alt="Ali Arghyani logo" />
<template #fallback>
<NuxtImg src="/favicon/android-chrome-192x192.png" alt="Ali Arghyani logo" />
</template>
</ClientOnly>
<!-- ... -->
</div>
</UContainer>
</footer>
</template>
<script setup lang="ts">
const colorMode = useColorMode()
const logoSrc = computed(() => {
return colorMode.value === 'dark'
? '/favicon/android-chrome-192x192-dark.png'
: '/favicon/android-chrome-192x192.png'
})
</script>
```
**Solution 2: Use onMounted (Alternative)**:
```vue
<template>
<footer class="py-10">
<UContainer>
<div class="flex flex-col items-center gap-4">
<NuxtImg :src="logoSrc" alt="Ali Arghyani logo" />
<!-- ... -->
</div>
</UContainer>
</footer>
</template>
<script setup lang="ts">
const colorMode = useColorMode()
const isMounted = ref(false)
onMounted(() => {
isMounted.value = true
})
const logoSrc = computed(() => {
if (!isMounted.value) {
return '/favicon/android-chrome-192x192.png'
}
return colorMode.value === 'dark'
? '/favicon/android-chrome-192x192-dark.png'
: '/favicon/android-chrome-192x192.png'
})
</script>
```
**Recommendation**: Use Solution 1 (ClientOnly) as it's more explicit and follows Nuxt best practices.
### 3. Language Switcher Fix
**File**: `app/components/LanguageSwitcher.vue`
**Current Issues**:
1. `sr-only` class on value might cause ARIA conflicts
2. No proper route switching logic
3. Missing proper ARIA announcements
**Updated Implementation**:
```vue
<template>
<ClientOnly>
<USelect
v-model="model"
:items="items"
value-key="value"
size="sm"
color="primary"
variant="soft"
:highlight="false"
arrow
:trailing="true"
placeholder="Language"
class="px-1 w-[64px] sm:w-[76px] rounded-full ring-1 ring-gray-200/70 dark:ring-gray-700/60 backdrop-blur-md shadow-sm h-[25px]"
:ui="{
base: 'rounded-full',
trailingIcon: 'text-dimmed group-data-[state=open]:rotate-180 transition-transform duration-200',
content: 'min-w-fit'
}"
:aria-label="t('nav.languageSelector')"
>
<template #leading>
<UIcon :name="selectedIcon" class="text-[16px]" />
</template>
<template #item-leading="{ item }">
<UIcon :name="item.icon" class="text-[16px]" />
</template>
<template #item-label="{ item }">
<span>{{ item.label }}</span>
</template>
</USelect>
</ClientOnly>
</template>
<script setup lang="ts">
const { locale, setLocale } = useI18n()
const { t } = useI18n()
const switchLocalePath = useSwitchLocalePath()
const router = useRouter()
type LangValue = 'en' | 'fa'
type Item = { label: string; value: LangValue; icon: string }
const items = ref<Item[]>([
{ label: 'English', value: 'en', icon: 'i-twemoji-flag-united-states' },
{ label: 'فارسی', value: 'fa', icon: 'i-twemoji-flag-iran' }
])
const model = ref<LangValue>(locale.value as LangValue)
// Keep model in sync if locale changes elsewhere
watch(locale, (val) => {
if ((val as LangValue) !== model.value) {
model.value = val as LangValue
}
})
const selectedIcon = computed<string>(() =>
items.value.find(i => i.value === model.value)?.icon ?? 'i-twemoji-flag-united-states'
)
const { startLocaleSwitching } = useLocaleSwitching()
const loading = useLoadingIndicator()
// On selection change, navigate to the equivalent page in the new locale
watch(model, async (val, oldVal) => {
if (val === oldVal) return
startLocaleSwitching(600)
if (loading) {
loading.start()
}
// Get the path for the new locale
const newPath = switchLocalePath(val)
// Navigate to the new path
await router.push(newPath)
// Update locale
await setLocale(val)
if (loading) {
setTimeout(() => loading.finish(), 600)
}
})
</script>
```
**Key Changes**:
1. Removed `sr-only` from UI config
2. Use `switchLocalePath()` to get the correct route for the new locale
3. Navigate using `router.push()` before setting locale
4. Added proper ARIA label using i18n
5. Changed item labels to full language names for better UX
### 4. Blog Navigation Updates
**Files to Update**:
- `app/pages/blog/index.vue`
- `app/pages/blog/[...slug].vue`
- `app/components/blog/BlogCard.vue`
- `app/components/blog/BlogNavigation.vue`
**Pattern to Follow**:
```vue
<script setup lang="ts">
const { locale } = useI18n()
const localePath = useLocalePath()
// Fetch posts for current locale
const { data: posts } = await useAsyncData('blog-posts', () =>
queryContent(`${locale.value}/blog`)
.where({ draft: { $ne: true } })
.sort({ date: -1 })
.find()
)
// Generate localized link
const postLink = computed(() => localePath(`/blog/${post.value._path.split('/').pop()}`))
</script>
```
**Important**: All blog links must use `localePath()` to ensure correct locale prefix.
## Data Models
### Route Structure
**Before (prefix_except_default)**:
```
/ → English home
/blog → English blog
/blog/post-slug → English post
/fa → Persian home
/fa/blog → Persian blog
/fa/blog/post-slug → Persian post
```
**After (prefix)**:
```
/ → Redirect to /en
/en → English home
/en/blog → English blog
/en/blog/post-slug → English post
/fa → Persian home
/fa/blog → Persian blog
/fa/blog/post-slug → Persian post
```
### Content Query Pattern
**Current**:
```typescript
queryContent(`${locale.value}/blog`)
```
**This remains the same** because content structure matches locale codes.
## Error Handling
### 404 Handling for Missing Translations
When a blog post exists in one language but not another:
```vue
<script setup lang="ts">
const { locale } = useI18n()
const route = useRoute()
const slug = route.params.slug as string[]
const { data: post } = await useAsyncData(`blog-post-${slug.join('/')}`, async () => {
try {
return await queryContent(`${locale.value}/blog`)
.where({ _path: `/${locale.value}/blog/${slug.join('/')}` })
.findOne()
} catch (error) {
return null
}
})
// If post not found, check if it exists in other locale
if (!post.value) {
const otherLocale = locale.value === 'en' ? 'fa' : 'en'
const { data: otherPost } = await useAsyncData(`blog-post-other-${slug.join('/')}`, async () => {
try {
return await queryContent(`${otherLocale}/blog`)
.where({ _path: `/${otherLocale}/blog/${slug.join('/')}` })
.findOne()
} catch (error) {
return null
}
})
if (otherPost.value) {
// Show message: "This post is only available in [other language]"
// Provide link to switch language
} else {
// Post doesn't exist in any language
throw createError({ statusCode: 404, message: 'Post not found' })
}
}
</script>
```
### Redirect Handling
**Root Path Redirect**:
```typescript
// middleware/redirect-root.global.ts
export default defineNuxtRouteMiddleware((to) => {
if (to.path === '/') {
return navigateTo('/en', { redirectCode: 301 })
}
})
```
## Testing Strategy
### Manual Testing Checklist
**i18n Routing**:
- [ ] Navigate to `/` → should redirect to `/en`
- [ ] Navigate to `/en` → should show English home
- [ ] Navigate to `/fa` → should show Persian home
- [ ] Navigate to `/en/blog` → should show English blog listing
- [ ] Navigate to `/fa/blog` → should show Persian blog listing
- [ ] Navigate to `/en/blog/post-slug` → should show English post
- [ ] Navigate to `/fa/blog/post-slug` → should show Persian post
**Language Switching**:
- [ ] On home page, switch from English to Persian → should navigate to `/fa`
- [ ] On home page, switch from Persian to English → should navigate to `/en`
- [ ] On blog listing, switch languages → should navigate to equivalent blog page
- [ ] On blog post, switch languages → should navigate to equivalent post (if exists)
- [ ] On blog post (only in one language), switch languages → should show fallback message
**Hydration**:
- [ ] Load page in light mode → no hydration errors in console
- [ ] Load page in dark mode → no hydration errors in console
- [ ] Switch color mode → logo updates correctly
- [ ] Check Footer logo on initial load → no flashing or mismatch
**Accessibility**:
- [ ] Language switcher is keyboard navigable (Tab, Enter, Arrow keys)
- [ ] Language switcher has proper ARIA labels
- [ ] No ARIA warnings in console
- [ ] Screen reader announces language changes
**Vue Router**:
- [ ] No Vue Router warnings in console during navigation
- [ ] No Vue Router warnings when switching languages
- [ ] Browser back/forward buttons work correctly
- [ ] URL updates correctly on language switch
### Browser Console Checks
**Before Fixes**:
```
❌ [Vue Router warn]: No match found for location with path "/en/blog/post-slug"
❌ Hydration mismatch in <img>
❌ [ARIA] aria-hidden should not be used on focusable elements
```
**After Fixes**:
```
✅ No Vue Router warnings
✅ No hydration warnings
✅ No ARIA warnings
```
## Performance Considerations
### Impact of Strategy Change
**Before (prefix_except_default)**:
- English routes: shorter URLs (no prefix)
- Persian routes: longer URLs (with prefix)
**After (prefix)**:
- All routes: consistent length (with prefix)
- Slightly longer URLs for English (adds 3 characters: `/en`)
**SEO Impact**:
- Minimal impact (3 characters)
- Better for international SEO (explicit language in URL)
- Easier for search engines to understand language variants
### Caching Strategy
Route rules remain the same:
```typescript
routeRules: {
'/en/blog': { swr: 3600 },
'/fa/blog': { swr: 3600 },
'/en/blog/**': { swr: 3600 },
'/fa/blog/**': { swr: 3600 }
}
```
## Migration Plan
### Step 1: Update i18n Configuration
- Change strategy from `prefix_except_default` to `prefix`
- Update prerender routes to include `/en` prefix
### Step 2: Fix Footer Component
- Wrap colorMode-dependent content in `ClientOnly`
- Add fallback for SSR
### Step 3: Fix Language Switcher
- Remove `sr-only` from UI config
- Implement proper route switching with `switchLocalePath()`
- Add proper ARIA labels
### Step 4: Update Blog Components
- Verify all blog links use `localePath()`
- Test blog navigation with new route structure
### Step 5: Add Redirect Middleware
- Create middleware to redirect `/` to `/en`
- Test redirect behavior
### Step 6: Update Route Rules
- Update route rules to use `/en` prefix
- Update sitemap generation
### Step 7: Testing
- Run manual testing checklist
- Verify no console errors
- Test all navigation flows
## Rollback Plan
If issues arise:
1. Revert `strategy` to `prefix_except_default` in `nuxt.config.ts`
2. Revert Footer component changes
3. Revert Language Switcher changes
4. Clear browser cache and cookies
5. Restart dev server
## Summary
This design addresses all three main issues:
1. **Vue Router Warnings**: Fixed by changing i18n strategy to `prefix` for consistent route structure
2. **Hydration Mismatch**: Fixed by wrapping colorMode-dependent content in `ClientOnly`
3. **ARIA Warning**: Fixed by removing `sr-only` from focusable elements and using proper ARIA labels
The changes are minimal, focused, and maintain backward compatibility with the content structure. All blog functionality will continue to work with the new route structure.
@@ -0,0 +1,113 @@
# Requirements Document
## Introduction
This document specifies the requirements for fixing i18n routing and hydration issues in the Nuxt 4 portfolio application. The issues include Vue Router warnings when switching languages, hydration mismatches in the Footer component, and accessibility warnings in the language switcher.
## Glossary
- **i18n System**: The internationalization system using @nuxtjs/i18n module for bilingual support (English and Persian)
- **Hydration Mismatch**: A Vue.js error that occurs when server-rendered HTML doesn't match client-side rendered content
- **Vue Router**: The official router for Vue.js applications, integrated with Nuxt
- **Language Switcher**: The UI component that allows users to switch between English and Persian languages
- **Route Prefix**: The language code prefix in URLs (e.g., /en/blog or /fa/blog)
- **localePath**: A helper function from @nuxtjs/i18n that generates locale-aware paths
- **ColorMode**: The dark/light theme system using @nuxtjs/color-mode module
- **ARIA**: Accessible Rich Internet Applications attributes for accessibility
## Requirements
### Requirement 1: Fix Vue Router Warnings for Blog Routes
**User Story:** As a visitor switching languages, I want the blog routes to work correctly, so that I don't encounter navigation errors.
#### Acceptance Criteria
1. WHEN a visitor switches from English to Persian, THE i18n System SHALL correctly resolve blog post routes with /fa/ prefix
2. WHEN a visitor switches from Persian to English, THE i18n System SHALL correctly resolve blog post routes with /en/ prefix
3. THE i18n System SHALL use localePath() helper for all blog navigation links to maintain locale context
4. WHERE a blog post exists in the current language, THE i18n System SHALL navigate to the localized version
5. WHERE a blog post does not exist in the target language, THE i18n System SHALL display a fallback message or redirect to the available version
6. THE i18n System SHALL not generate Vue Router warnings in the browser console during language switching
7. THE i18n System SHALL maintain the current page context when switching languages (e.g., stay on blog listing when switching from /blog to /fa/blog)
### Requirement 2: Fix Hydration Mismatch in Footer Component
**User Story:** As a visitor loading the page, I want the Footer to render without hydration errors, so that I have a smooth initial page load experience.
#### Acceptance Criteria
1. WHEN the page initially loads, THE Footer Component SHALL render identical HTML on server and client
2. THE Footer Component SHALL handle colorMode state without causing hydration mismatches
3. WHERE colorMode is accessed during SSR, THE Footer Component SHALL use a consistent default value
4. THE Footer Component SHALL update colorMode-dependent content only after hydration is complete
5. THE Footer Component SHALL not generate hydration mismatch warnings in the browser console
6. THE Footer Component SHALL display correctly in both light and dark modes after hydration
### Requirement 3: Fix ARIA Accessibility Warning in Language Switcher
**User Story:** As a visitor using assistive technology, I want the language switcher to be properly accessible, so that I can switch languages without accessibility issues.
#### Acceptance Criteria
1. THE Language Switcher SHALL not use aria-hidden on focusable or interactive elements
2. WHERE aria-hidden is used, THE Language Switcher SHALL ensure the element is not focusable
3. THE Language Switcher SHALL provide appropriate ARIA labels for screen readers
4. THE Language Switcher SHALL use semantic HTML for language selection
5. THE Language Switcher SHALL be keyboard navigable (Tab, Enter, Space keys)
6. THE Language Switcher SHALL announce language changes to screen readers
7. THE Language Switcher SHALL not generate ARIA-related warnings in the browser console
### Requirement 4: Improve i18n Route Configuration
**User Story:** As a developer, I want the i18n routing configuration to be correct, so that all routes work properly with language prefixes.
#### Acceptance Criteria
1. THE i18n System SHALL configure route prefixes correctly in nuxt.config.ts
2. THE i18n System SHALL use strategy: 'prefix' or 'prefix_except_default' for consistent URL structure
3. THE i18n System SHALL define all routes with proper locale prefixes
4. THE i18n System SHALL handle dynamic routes (like blog slugs) with locale awareness
5. THE i18n System SHALL provide fallback routes when content is not available in a locale
6. THE i18n System SHALL generate correct sitemap with all localized routes
### Requirement 5: Ensure Consistent Client-Server Rendering
**User Story:** As a visitor, I want the page to load without visual flashes or content shifts, so that I have a smooth browsing experience.
#### Acceptance Criteria
1. WHEN the page loads, THE Application SHALL render identical content on server and client
2. THE Application SHALL defer client-only content until after hydration using ClientOnly component
3. WHERE dynamic content depends on browser APIs, THE Application SHALL use onMounted lifecycle hook
4. THE Application SHALL not cause layout shifts during hydration
5. THE Application SHALL handle localStorage and cookies consistently between server and client
6. THE Application SHALL not generate Suspense-related warnings during hydration
### Requirement 6: Fix Language Switcher Implementation
**User Story:** As a visitor, I want to switch languages smoothly, so that I can view content in my preferred language.
#### Acceptance Criteria
1. WHEN a visitor clicks the language switcher, THE Application SHALL navigate to the equivalent page in the target language
2. THE Language Switcher SHALL use switchLocalePath() helper from @nuxtjs/i18n
3. THE Language Switcher SHALL maintain the current route context (e.g., /blog/post-slug becomes /fa/blog/post-slug)
4. WHERE the current page doesn't exist in the target language, THE Language Switcher SHALL navigate to the home page of that locale
5. THE Language Switcher SHALL update the HTML lang attribute
6. THE Language Switcher SHALL update the document direction (ltr/rtl) for Persian
7. THE Language Switcher SHALL provide visual feedback during language switching
### Requirement 7: Testing and Validation
**User Story:** As a developer, I want to verify that all i18n and routing issues are resolved, so that I can ensure a quality user experience.
#### Acceptance Criteria
1. THE Application SHALL pass manual testing for language switching on all pages
2. THE Application SHALL not generate any hydration warnings in the browser console
3. THE Application SHALL not generate any Vue Router warnings in the browser console
4. THE Application SHALL not generate any ARIA accessibility warnings in the browser console
5. THE Application SHALL maintain proper URL structure with locale prefixes
6. THE Application SHALL handle browser back/forward navigation correctly with localized routes
7. THE Application SHALL work correctly in both development and production builds
+101
View File
@@ -0,0 +1,101 @@
# Implementation Plan
This implementation plan breaks down the i18n routing and hydration fixes into discrete, actionable coding tasks. Each task builds incrementally on previous work, with all code integrated and functional at each step.
## Task List
- [x] 1. Update i18n configuration to use prefix strategy
- Change strategy from 'prefix_except_default' to 'prefix' in nuxt.config.ts
- Update prerender routes to include /en prefix (/en/blog instead of /blog)
- Update route rules to use /en prefix for caching
- Verify configuration by checking generated routes in dev mode
- _Requirements: 1.1, 1.2, 1.3, 4.1, 4.2, 4.3_
- [x] 2. Create redirect middleware for root path
- Create middleware/redirect-root.global.ts file
- Implement redirect from / to /en with 301 status code
- Test redirect behavior in browser
- _Requirements: 4.5_
- [x] 3. Fix Footer component hydration mismatch
- Wrap NuxtImg with colorMode-dependent src in ClientOnly component
- Add fallback template with default logo for SSR
- Remove colorMode.unknown check (no longer needed)
- Test in both light and dark modes
- Verify no hydration warnings in console
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 5.1, 5.2_
- [ ] 4. Fix Language Switcher ARIA and routing issues
- Remove 'sr-only' from :ui config in LanguageSwitcher.vue
- Add proper aria-label using i18n translation key
- Import and use switchLocalePath() composable
- Update watch logic to use switchLocalePath() for route generation
- Navigate to new path using router.push() before setLocale()
- Change item labels from 'en'/'fa' to 'English'/'فارسی' for better UX
- Test language switching on all pages (home, blog listing, blog post)
- Verify no ARIA warnings in console
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7_
- [ ] 5. Update TopNav component for new route structure
- Verify all navigation links use localePath() helper
- Update blog link to use localePath('/blog')
- Update home navigation to use localePath('/')
- Test navigation from all sections
- _Requirements: 1.3, 4.4_
- [ ] 6. Verify blog components use localePath correctly
- Check BlogCard.vue uses localePath for post links
- Check BlogNavigation.vue uses localePath for prev/next links
- Check blog/index.vue uses localePath for navigation
- Check blog/[...slug].vue uses localePath for breadcrumbs and back link
- Fix any hardcoded paths to use localePath()
- _Requirements: 1.3, 1.4, 6.3_
- [ ] 7. Add i18n translation key for language selector
- Add 'nav.languageSelector' key to i18n/locales/en.json
- Add 'nav.languageSelector' key to i18n/locales/fa.json
- _Requirements: 3.3_
- [ ] 8. Test and verify all fixes
- Test root path redirect (/ → /en)
- Test language switching on home page
- Test language switching on blog listing page
- Test language switching on blog post page
- Test browser back/forward navigation
- Verify no hydration warnings in console
- Verify no Vue Router warnings in console
- Verify no ARIA warnings in console
- Test in both light and dark modes
- Test keyboard navigation for language switcher
- _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7_
- [ ]* 9. Add fallback handling for missing translations
- In blog/[...slug].vue, add logic to check if post exists in other locale
- Display message when post is only available in other language
- Provide link to switch to the language where post exists
- Test with posts that exist in only one language
- _Requirements: 1.5, 4.5_
- [ ]* 10. Update sitemap generation for new route structure
- Update sitemap configuration to include /en prefix
- Verify all routes are included in sitemap
- Test sitemap generation in build
- _Requirements: 4.6_