diff --git a/api/app/Http/Controllers/Api/ProjectController.php b/api/app/Http/Controllers/Api/ProjectController.php new file mode 100644 index 0000000..497bfd4 --- /dev/null +++ b/api/app/Http/Controllers/Api/ProjectController.php @@ -0,0 +1,26 @@ +ordered()->get(); + + return ProjectResource::collection($projects) + ->additional(['meta' => ['lang' => app()->getLocale()]]); + } + + public function show(string $slug) + { + $project = Project::with('skills')->where('slug', $slug)->firstOrFail(); + + return (new ProjectResource($project)) + ->additional(['meta' => ['lang' => app()->getLocale()]]); + } +} diff --git a/api/app/Http/Controllers/Api/SkillController.php b/api/app/Http/Controllers/Api/SkillController.php new file mode 100644 index 0000000..8763c1a --- /dev/null +++ b/api/app/Http/Controllers/Api/SkillController.php @@ -0,0 +1,22 @@ +get()->groupBy('category'); + + $grouped = $skills->map(fn ($group) => SkillResource::collection($group)); + + return response()->json([ + 'data' => $grouped, + 'meta' => ['lang' => app()->getLocale()], + ]); + } +} diff --git a/api/app/Http/Middleware/SetLocale.php b/api/app/Http/Middleware/SetLocale.php new file mode 100644 index 0000000..dc3fd5b --- /dev/null +++ b/api/app/Http/Middleware/SetLocale.php @@ -0,0 +1,50 @@ +parseAcceptLanguage($request->header('Accept-Language')); + + app()->setLocale($locale); + $request->attributes->set('locale', $locale); + + return $next($request); + } + + protected function parseAcceptLanguage(?string $header): string + { + if (!$header) { + return $this->fallbackLocale; + } + + $locales = []; + foreach (explode(',', $header) as $part) { + $part = trim($part); + if (preg_match('/^([a-z]{2})(?:-[A-Z]{2})?(?:;q=([0-9.]+))?$/i', $part, $matches)) { + $lang = strtolower($matches[1]); + $quality = isset($matches[2]) ? (float) $matches[2] : 1.0; + $locales[$lang] = $quality; + } + } + + arsort($locales); + + foreach (array_keys($locales) as $lang) { + if (in_array($lang, $this->supportedLocales)) { + return $lang; + } + } + + return $this->fallbackLocale; + } +} diff --git a/api/app/Http/Resources/ProjectResource.php b/api/app/Http/Resources/ProjectResource.php new file mode 100644 index 0000000..90c1e86 --- /dev/null +++ b/api/app/Http/Resources/ProjectResource.php @@ -0,0 +1,26 @@ + $this->id, + 'slug' => $this->slug, + 'title' => $this->getTranslated('title_key'), + 'description' => $this->getTranslated('description_key'), + 'short_description' => $this->getTranslated('short_description_key'), + 'image' => $this->image, + 'url' => $this->url, + 'github_url' => $this->github_url, + 'date_completed' => $this->date_completed?->format('Y-m-d'), + 'is_featured' => $this->is_featured, + 'skills' => SkillResource::collection($this->whenLoaded('skills')), + ]; + } +} diff --git a/api/app/Http/Resources/SkillResource.php b/api/app/Http/Resources/SkillResource.php new file mode 100644 index 0000000..6f2f80f --- /dev/null +++ b/api/app/Http/Resources/SkillResource.php @@ -0,0 +1,26 @@ + $this->id, + 'slug' => $this->slug, + 'name' => $this->getTranslated('name_key'), + 'description' => $this->getTranslated('description_key'), + 'icon' => $this->icon, + 'category' => $this->category, + 'max_level' => $this->max_level, + 'pivot' => $this->when($this->pivot, fn () => [ + 'level_before' => $this->pivot->level_before, + 'level_after' => $this->pivot->level_after, + ]), + ]; + } +} diff --git a/api/app/Models/Project.php b/api/app/Models/Project.php index 5bedf50..90995d6 100644 --- a/api/app/Models/Project.php +++ b/api/app/Models/Project.php @@ -2,12 +2,14 @@ namespace App\Models; +use App\Traits\HasTranslations; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Project extends Model { + use HasTranslations; protected $fillable = [ 'slug', 'title_key', diff --git a/api/app/Models/Skill.php b/api/app/Models/Skill.php index c863540..4f6179b 100644 --- a/api/app/Models/Skill.php +++ b/api/app/Models/Skill.php @@ -2,12 +2,14 @@ namespace App\Models; +use App\Traits\HasTranslations; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Skill extends Model { + use HasTranslations; protected $fillable = [ 'slug', 'name_key', diff --git a/api/app/Traits/HasTranslations.php b/api/app/Traits/HasTranslations.php new file mode 100644 index 0000000..11a0fdb --- /dev/null +++ b/api/app/Traits/HasTranslations.php @@ -0,0 +1,20 @@ +attributes->get('locale', 'fr'); + $key = $this->{$keyField}; + + if (!$key) { + return null; + } + + return Translation::getTranslation($key, $lang); + } +} diff --git a/api/bootstrap/app.php b/api/bootstrap/app.php index 2661f8c..2e1aca9 100644 --- a/api/bootstrap/app.php +++ b/api/bootstrap/app.php @@ -12,6 +12,7 @@ return Application::configure(basePath: dirname(__DIR__)) ) ->withMiddleware(function (Middleware $middleware): void { $middleware->api(append: [ + \App\Http\Middleware\SetLocale::class, \App\Http\Middleware\VerifyApiKey::class, ]); }) diff --git a/api/routes/api.php b/api/routes/api.php index 7c3dfac..1730798 100644 --- a/api/routes/api.php +++ b/api/routes/api.php @@ -1,7 +1,13 @@ json(['status' => 'ok']); }); + +Route::get('/projects', [ProjectController::class, 'index']); +Route::get('/projects/{slug}', [ProjectController::class, 'show']); +Route::get('/skills', [SkillController::class, 'index']); diff --git a/docs/implementation-artifacts/1-3-systeme-i18n-frontend-api-bilingue.md b/docs/implementation-artifacts/1-3-systeme-i18n-frontend-api-bilingue.md index 4d87712..89e86b0 100644 --- a/docs/implementation-artifacts/1-3-systeme-i18n-frontend-api-bilingue.md +++ b/docs/implementation-artifacts/1-3-systeme-i18n-frontend-api-bilingue.md @@ -1,6 +1,6 @@ # Story 1.3: Système i18n frontend + API bilingue -Status: ready-for-dev +Status: review ## Story @@ -22,28 +22,28 @@ so that je comprends le contenu. ## Tasks / Subtasks -- [ ] **Task 1: Configuration @nuxtjs/i18n** (AC: #1, #2, #3, #4) - - [ ] Vérifier que `@nuxtjs/i18n` est installé (Story 1.1) - - [ ] Créer la structure `frontend/i18n/` pour les fichiers de traduction - - [ ] Configurer `nuxt.config.ts` avec i18n complet : +- [x] **Task 1: Configuration @nuxtjs/i18n** (AC: #1, #2, #3, #4) + - [x] Vérifier que `@nuxtjs/i18n` est installé (Story 1.1) + - [x] Créer la structure `frontend/i18n/` pour les fichiers de traduction + - [x] Configurer `nuxt.config.ts` avec i18n complet : - locales: ['fr', 'en'] - defaultLocale: 'fr' - strategy: 'prefix_except_default' - detectBrowserLanguage: false (on utilise l'URL) - - [ ] Activer `vueI18n` pour le composant `` + - [x] Activer `vueI18n` pour le composant `` -- [ ] **Task 2: Fichiers de traduction JSON** (AC: #1) - - [ ] Créer `frontend/i18n/fr.json` avec structure de base - - [ ] Créer `frontend/i18n/en.json` avec structure de base - - [ ] Inclure les traductions pour : +- [x] **Task 2: Fichiers de traduction JSON** (AC: #1) + - [x] Créer `frontend/i18n/fr.json` avec structure de base + - [x] Créer `frontend/i18n/en.json` avec structure de base + - [x] Inclure les traductions pour : - Navigation (Accueil, Projets, Compétences, Témoignages, Parcours, Contact) - Boutons communs (Continuer, Retour, Découvrir, Fermer) - Messages d'erreur (404, erreur générique) - Landing page (accroche, CTA Aventure, CTA Express) - Footer et metadata -- [ ] **Task 3: Routes localisées Nuxt** (AC: #2, #3) - - [ ] Configurer `i18n.pages` dans nuxt.config.ts pour les routes custom : +- [x] **Task 3: Routes localisées Nuxt** (AC: #2, #3) + - [x] Configurer `i18n.pages` dans nuxt.config.ts pour les routes custom : ``` pages: { 'projets/[slug]': { en: '/projects/[slug]' }, @@ -53,85 +53,85 @@ so that je comprends le contenu. 'contact': { en: '/contact' } } ``` - - [ ] Vérifier que les routes FR fonctionnent sans préfixe - - [ ] Vérifier que les routes EN fonctionnent avec préfixe `/en` + - [x] Vérifier que les routes FR fonctionnent sans préfixe + - [x] Vérifier que les routes EN fonctionnent avec préfixe `/en` -- [ ] **Task 4: Helpers i18n et composables** (AC: #4) - - [ ] Créer un composable `frontend/app/composables/useLocale.ts` pour centraliser la logique i18n - - [ ] Exposer : `currentLocale`, `switchLocale()`, `localizedPath()` - - [ ] Tester `useI18n()` dans un composant - - [ ] Tester `$t('key')` dans un template - - [ ] Tester `localePath('/projets')` pour les liens - - [ ] Tester `switchLocalePath('en')` pour le switcher de langue +- [x] **Task 4: Helpers i18n et composables** (AC: #4) + - [x] Créer un composable `frontend/app/composables/useLocale.ts` pour centraliser la logique i18n + - [x] Exposer : `currentLocale`, `switchLocale()`, `localizedPath()` + - [x] Tester `useI18n()` dans un composant + - [x] Tester `$t('key')` dans un template + - [x] Tester `localePath('/projets')` pour les liens + - [x] Tester `switchLocalePath('en')` pour le switcher de langue -- [ ] **Task 5: SEO et balises hreflang** (AC: #5, #6) - - [ ] Configurer `i18n.head` dans nuxt.config.ts pour les balises SEO - - [ ] Vérifier que `` ou `` est dynamique - - [ ] Vérifier les balises `` - - [ ] Vérifier les balises `` - - [ ] Vérifier `` +- [x] **Task 5: SEO et balises hreflang** (AC: #5, #6) + - [x] Configurer `i18n.head` dans nuxt.config.ts pour les balises SEO + - [x] Vérifier que `` ou `` est dynamique + - [x] Vérifier les balises `` + - [x] Vérifier les balises `` + - [x] Vérifier `` -- [ ] **Task 6: Composant LanguageSwitcher** (AC: #4) - - [ ] Créer `frontend/app/components/ui/LanguageSwitcher.vue` - - [ ] Afficher les langues disponibles (FR / EN) - - [ ] Utiliser `switchLocalePath()` pour la navigation - - [ ] Highlight de la langue active - - [ ] Accessible au clavier (boutons ou liens) - - [ ] Style cohérent avec le design system (sky-accent pour actif) +- [x] **Task 6: Composant LanguageSwitcher** (AC: #4) + - [x] Créer `frontend/app/components/ui/LanguageSwitcher.vue` + - [x] Afficher les langues disponibles (FR / EN) + - [x] Utiliser `switchLocalePath()` pour la navigation + - [x] Highlight de la langue active + - [x] Accessible au clavier (boutons ou liens) + - [x] Style cohérent avec le design system (sky-accent pour actif) -- [ ] **Task 7: Middleware Laravel SetLocale** (AC: #7, #9) - - [ ] Créer `api/app/Http/Middleware/SetLocale.php` - - [ ] Extraire la langue depuis le header `Accept-Language` - - [ ] Parser le header (ex: `fr-FR,fr;q=0.9,en;q=0.8` → `fr`) - - [ ] Valider que la langue est supportée (fr, en) - - [ ] Fallback vers `fr` si langue non supportée - - [ ] Stocker la langue dans `app()->setLocale($lang)` - - [ ] Passer la langue via `$request->attributes->set('lang', $lang)` - - [ ] Enregistrer le middleware dans `bootstrap/app.php` pour les routes API +- [x] **Task 7: Middleware Laravel SetLocale** (AC: #7, #9) + - [x] Créer `api/app/Http/Middleware/SetLocale.php` + - [x] Extraire la langue depuis le header `Accept-Language` + - [x] Parser le header (ex: `fr-FR,fr;q=0.9,en;q=0.8` → `fr`) + - [x] Valider que la langue est supportée (fr, en) + - [x] Fallback vers `fr` si langue non supportée + - [x] Stocker la langue dans `app()->setLocale($lang)` + - [x] Passer la langue via `$request->attributes->set('lang', $lang)` + - [x] Enregistrer le middleware dans `bootstrap/app.php` pour les routes API -- [ ] **Task 8: Trait HasTranslations pour les Models** (AC: #8) - - [ ] Créer `api/app/Traits/HasTranslations.php` - - [ ] Méthode `getTranslated($keyField, $lang = null)` qui : +- [x] **Task 8: Trait HasTranslations pour les Models** (AC: #8) + - [x] Créer `api/app/Traits/HasTranslations.php` + - [x] Méthode `getTranslated($keyField, $lang = null)` qui : - Récupère la clé depuis le champ (ex: `$this->title_key`) - Joint la table `translations` pour obtenir la valeur - Utilise la langue du request ou le fallback - - [ ] Appliquer le trait aux models : Project, Skill - - [ ] Tester : `$project->getTranslated('title_key', 'fr')` + - [x] Appliquer le trait aux models : Project, Skill + - [x] Tester : `$project->getTranslated('title_key', 'fr')` -- [ ] **Task 9: API Resources avec traductions** (AC: #8) - - [ ] Créer `api/app/Http/Resources/ProjectResource.php` - - [ ] Transformer les champs `*_key` en valeurs traduites : +- [x] **Task 9: API Resources avec traductions** (AC: #8) + - [x] Créer `api/app/Http/Resources/ProjectResource.php` + - [x] Transformer les champs `*_key` en valeurs traduites : ```php 'title' => $this->getTranslated('title_key'), 'description' => $this->getTranslated('description_key'), ``` - - [ ] Créer `api/app/Http/Resources/SkillResource.php` de même - - [ ] Inclure `meta.lang` dans les réponses pour debug/vérification + - [x] Créer `api/app/Http/Resources/SkillResource.php` de même + - [x] Inclure `meta.lang` dans les réponses pour debug/vérification -- [ ] **Task 10: Endpoints API avec traductions** (AC: #7, #8) - - [ ] Créer `api/app/Http/Controllers/Api/ProjectController.php` - - [ ] Endpoint `GET /api/projects` retournant la liste traduite - - [ ] Endpoint `GET /api/projects/{slug}` retournant le détail traduit - - [ ] Créer `api/app/Http/Controllers/Api/SkillController.php` - - [ ] Endpoint `GET /api/skills` retournant la liste traduite par catégorie - - [ ] Enregistrer les routes dans `routes/api.php` +- [x] **Task 10: Endpoints API avec traductions** (AC: #7, #8) + - [x] Créer `api/app/Http/Controllers/Api/ProjectController.php` + - [x] Endpoint `GET /api/projects` retournant la liste traduite + - [x] Endpoint `GET /api/projects/{slug}` retournant le détail traduit + - [x] Créer `api/app/Http/Controllers/Api/SkillController.php` + - [x] Endpoint `GET /api/skills` retournant la liste traduite par catégorie + - [x] Enregistrer les routes dans `routes/api.php` -- [ ] **Task 11: Intégration frontend-backend** (AC: tous) - - [ ] Créer composable `frontend/app/composables/useApi.ts` qui : +- [x] **Task 11: Intégration frontend-backend** (AC: tous) + - [x] Créer composable `frontend/app/composables/useApi.ts` qui : - Utilise `$fetch` ou `useFetch` de Nuxt - Ajoute automatiquement le header `X-API-Key` - Ajoute automatiquement le header `Accept-Language` selon la locale courante - - [ ] Tester un appel API depuis une page Nuxt - - [ ] Vérifier que le contenu retourné est dans la bonne langue + - [x] Tester un appel API depuis une page Nuxt + - [x] Vérifier que le contenu retourné est dans la bonne langue -- [ ] **Task 12: Validation finale** (AC: tous) - - [ ] Accéder à `/` → contenu FR - - [ ] Accéder à `/en` → contenu EN - - [ ] Cliquer sur le switcher FR → EN → URL change vers `/en` - - [ ] API call avec `Accept-Language: en` → réponse en anglais - - [ ] API call avec `Accept-Language: de` → fallback FR - - [ ] Vérifier les balises hreflang dans le code source HTML - - [ ] Vérifier `` dynamique +- [x] **Task 12: Validation finale** (AC: tous) + - [x] Accéder à `/` → contenu FR + - [x] Accéder à `/en` → contenu EN + - [x] Cliquer sur le switcher FR → EN → URL change vers `/en` + - [x] API call avec `Accept-Language: en` → réponse en anglais + - [x] API call avec `Accept-Language: de` → fallback FR + - [x] Vérifier les balises hreflang dans le code source HTML + - [x] Vérifier `` dynamique ## Dev Notes @@ -459,16 +459,49 @@ api/ ### Agent Model Used -{{agent_model_name_version}} +Claude Opus 4.5 (claude-opus-4-5-20251101) ### Debug Log References +- hreflang tags non générés au début — résolu en ajoutant `useLocaleHead()` dans `app.vue` avec `addSeoAttributes: true` +- `langDir` configuré en `'../i18n/'` (relatif à `app/`) car Nuxt 4 utilise `app/` comme srcDir + ### Completion Notes List +- Frontend: i18n configuré avec lazy loading, fichiers JSON FR/EN, routes localisées, `useLocaleHead()` pour SEO +- Frontend: Composable `useLocale` encapsule la logique i18n, composable `useApi` ajoute automatiquement les headers +- Frontend: LanguageSwitcher accessible au clavier avec highlight de la langue active +- Backend: Middleware SetLocale parse `Accept-Language` avec support qualité (q=), fallback FR +- Backend: Trait HasTranslations appliqué à Project et Skill +- Backend: API Resources transforment les `*_key` en valeurs traduites, meta.lang inclus +- Backend: Controllers avec endpoints GET /api/projects, GET /api/projects/{slug}, GET /api/skills +- Validé: `/` → FR, `/en` → EN, hreflang tags, `` / `` +- Validé: API avec Accept-Language: fr → FR, en → EN, de → fallback FR + ### Change Log | Date | Change | Author | |------|--------|--------| | 2026-02-03 | Story créée avec contexte complet | SM Agent | +| 2026-02-05 | Tasks 1-12 implémentées et validées | Dev Agent (Claude Opus 4.5) | ### File List +- `frontend/i18n/fr.json` — CRÉÉ +- `frontend/i18n/en.json` — CRÉÉ +- `frontend/nuxt.config.ts` — MODIFIÉ (config i18n complète) +- `frontend/app/app.vue` — MODIFIÉ (useLocaleHead pour SEO) +- `frontend/app/pages/index.vue` — MODIFIÉ (utilise $t()) +- `frontend/app/composables/useLocale.ts` — CRÉÉ +- `frontend/app/composables/useApi.ts` — CRÉÉ +- `frontend/app/components/ui/LanguageSwitcher.vue` — CRÉÉ +- `api/app/Http/Middleware/SetLocale.php` — CRÉÉ +- `api/app/Traits/HasTranslations.php` — CRÉÉ +- `api/app/Http/Resources/ProjectResource.php` — CRÉÉ +- `api/app/Http/Resources/SkillResource.php` — CRÉÉ +- `api/app/Http/Controllers/Api/ProjectController.php` — CRÉÉ +- `api/app/Http/Controllers/Api/SkillController.php` — CRÉÉ +- `api/bootstrap/app.php` — MODIFIÉ (ajout middleware SetLocale) +- `api/routes/api.php` — MODIFIÉ (ajout routes projects/skills) +- `api/app/Models/Project.php` — MODIFIÉ (ajout HasTranslations trait) +- `api/app/Models/Skill.php` — MODIFIÉ (ajout HasTranslations trait) + diff --git a/docs/implementation-artifacts/sprint-status.yaml b/docs/implementation-artifacts/sprint-status.yaml index bd72b33..99ad0fb 100644 --- a/docs/implementation-artifacts/sprint-status.yaml +++ b/docs/implementation-artifacts/sprint-status.yaml @@ -46,7 +46,7 @@ development_status: epic-1: in-progress 1-1-initialisation-monorepo-infrastructure: review 1-2-base-donnees-migrations-initiales: review - 1-3-systeme-i18n-frontend-api-bilingue: ready-for-dev + 1-3-systeme-i18n-frontend-api-bilingue: review 1-4-layouts-routing-transitions-page: ready-for-dev 1-5-landing-page-choix-heros: ready-for-dev 1-6-store-pinia-progression-bandeau-rgpd: ready-for-dev diff --git a/frontend/app/app.vue b/frontend/app/app.vue index f8eacfa..28da780 100644 --- a/frontend/app/app.vue +++ b/frontend/app/app.vue @@ -3,3 +3,16 @@ + + diff --git a/frontend/app/components/ui/LanguageSwitcher.vue b/frontend/app/components/ui/LanguageSwitcher.vue new file mode 100644 index 0000000..d557d16 --- /dev/null +++ b/frontend/app/components/ui/LanguageSwitcher.vue @@ -0,0 +1,22 @@ + + + diff --git a/frontend/app/composables/useApi.ts b/frontend/app/composables/useApi.ts new file mode 100644 index 0000000..9739c52 --- /dev/null +++ b/frontend/app/composables/useApi.ts @@ -0,0 +1,18 @@ +export const useApi = () => { + const config = useRuntimeConfig() + const { locale } = useI18n() + + const apiFetch = async (endpoint: string, options: Record = {}) => { + return await $fetch(`${config.public.apiUrl}${endpoint}`, { + ...options, + headers: { + 'X-API-Key': config.public.apiKey as string, + 'Accept-Language': locale.value, + 'Content-Type': 'application/json', + ...options.headers, + }, + }) + } + + return { apiFetch } +} diff --git a/frontend/app/composables/useLocale.ts b/frontend/app/composables/useLocale.ts new file mode 100644 index 0000000..ff7766f --- /dev/null +++ b/frontend/app/composables/useLocale.ts @@ -0,0 +1,29 @@ +export const useLocale = () => { + const { locale, locales, setLocale } = useI18n() + const switchLocalePath = useSwitchLocalePath() + const localePath = useLocalePath() + + const currentLocale = computed(() => locale.value) + + const availableLocales = computed(() => + (locales.value as Array<{ code: string; name: string }>).map(l => ({ + code: l.code, + name: l.name, + })) + ) + + const switchLocale = (code: string) => { + return navigateTo(switchLocalePath(code)) + } + + const localizedPath = (path: string) => { + return localePath(path) + } + + return { + currentLocale, + availableLocales, + switchLocale, + localizedPath, + } +} diff --git a/frontend/app/pages/index.vue b/frontend/app/pages/index.vue index 29c4832..75b4699 100644 --- a/frontend/app/pages/index.vue +++ b/frontend/app/pages/index.vue @@ -1,5 +1,14 @@ diff --git a/frontend/i18n/en.json b/frontend/i18n/en.json new file mode 100644 index 0000000..dc947a1 --- /dev/null +++ b/frontend/i18n/en.json @@ -0,0 +1,37 @@ +{ + "nav": { + "home": "Home", + "projects": "Projects", + "skills": "Skills", + "testimonials": "Testimonials", + "journey": "Journey", + "contact": "Contact", + "resume": "Quick Resume" + }, + "common": { + "continue": "Continue", + "back": "Back", + "discover": "Discover", + "close": "Close", + "loading": "Loading...", + "language": "Language" + }, + "landing": { + "title": "Welcome to my universe", + "subtitle": "Full-Stack Developer", + "cta_adventure": "Start the adventure", + "cta_express": "Express mode" + }, + "error": { + "404": "Page not found", + "generic": "An error occurred" + }, + "meta": { + "title": "Skycel - Célian's Portfolio", + "description": "Discover my interactive and gamified portfolio" + }, + "footer": { + "copyright": "© {year} Célian — Skycel", + "built_with": "Built with Nuxt & Laravel" + } +} diff --git a/frontend/i18n/fr.json b/frontend/i18n/fr.json new file mode 100644 index 0000000..633ef9f --- /dev/null +++ b/frontend/i18n/fr.json @@ -0,0 +1,37 @@ +{ + "nav": { + "home": "Accueil", + "projects": "Projets", + "skills": "Compétences", + "testimonials": "Témoignages", + "journey": "Parcours", + "contact": "Contact", + "resume": "Résumé Express" + }, + "common": { + "continue": "Continuer", + "back": "Retour", + "discover": "Découvrir", + "close": "Fermer", + "loading": "Chargement...", + "language": "Langue" + }, + "landing": { + "title": "Bienvenue dans mon univers", + "subtitle": "Développeur Full-Stack", + "cta_adventure": "Partir à l'aventure", + "cta_express": "Mode express" + }, + "error": { + "404": "Page non trouvée", + "generic": "Une erreur est survenue" + }, + "meta": { + "title": "Skycel - Portfolio de Célian", + "description": "Découvrez mon portfolio interactif et gamifié" + }, + "footer": { + "copyright": "© {year} Célian — Skycel", + "built_with": "Construit avec Nuxt & Laravel" + } +} diff --git a/frontend/nuxt.config.ts b/frontend/nuxt.config.ts index 6a63c5f..e0b6060 100644 --- a/frontend/nuxt.config.ts +++ b/frontend/nuxt.config.ts @@ -24,9 +24,25 @@ export default defineNuxtConfig({ }, i18n: { - locales: ['fr', 'en'], + locales: [ + { code: 'fr', iso: 'fr-FR', file: 'fr.json', name: 'Français' }, + { code: 'en', iso: 'en-US', file: 'en.json', name: 'English' }, + ], defaultLocale: 'fr', strategy: 'prefix_except_default', + lazy: true, + langDir: '../i18n/', + detectBrowserLanguage: false, + pages: { + 'projets/index': { en: '/projects' }, + 'projets/[slug]': { en: '/projects/[slug]' }, + 'competences': { en: '/skills' }, + 'temoignages': { en: '/testimonials' }, + 'parcours': { en: '/journey' }, + 'contact': { en: '/contact' }, + 'resume': { en: '/resume' }, + }, + baseUrl: process.env.NUXT_PUBLIC_SITE_URL || 'https://skycel.fr', }, app: {