--- phase: 08-content-cocon-semantique plan: 01 subsystem: content/ui tags: [nuxt-content, i18n, hytale, cocon-semantique, blog] requirements: [SEO-14] requires: - BlogCard.vue (variant compact) - queryCollection('blog_fr'|'blog_en') collections Phase 5 - i18n locales fr/en provides: - HytaleRecentArticles.vue (section auto-importee, masquee si 0 article hytale) - i18n hytale.recentArticles.{title,subtitle,viewAll} - injection en fin de /hytale affects: - app/pages/hytale.vue tech-stack: added: [] patterns: - queryCollection bilingual literal branches (Phase 5 Pitfall D-03) - JS post-filter tags.includes('hytale') + slice(0,2) (D-11) - v-if="articles.length" hide-if-empty (D-12) key-files: created: - app/components/HytaleRecentArticles.vue modified: - app/pages/hytale.vue - i18n/locales/fr.json - i18n/locales/en.json decisions: - Filtre JS tags.includes('hytale') choisi sur LIKE SQL (D-11 — LIKE unreliable sur JSON array SQLite) - Style accentue pour cles i18n recentArticles (aligne blog.* 2026, ecart avec hytale.* legacy ASCII accepte) - Injection apres TestimonialsSection, dernier enfant du root div de /hytale - BlogCard variant=compact sans prop direction (default 'next' accepte, pas de semantique prev/next dans ce contexte) metrics: duration: ~5 min completed: 2026-04-22 tasks: 2 files: 4 --- # Phase 8 Plan 01: Scaffold HytaleRecentArticles — Summary Scaffolding du cocon semantique cote /hytale : composant `HytaleRecentArticles.vue` (queryCollection bilingue + filtre JS tag `hytale` + limit 2), injection dans `/hytale`, cles i18n FR/EN. Section degrade gracieusement (v-if) tant que 0 article publie, permettant de shipper Wave 1 sans rompre /hytale. ## Changes ### Task 1 — HytaleRecentArticles.vue (commit `ddfc685`) Nouveau composant `app/components/HytaleRecentArticles.vue` (72 lignes, auto-importe) : - **Script** : branches litterales `queryCollection('blog_fr')` / `queryCollection('blog_en')` sur ternaire `isFr.value` (Phase 5 Pitfall D-03), key `hytale-recent-${locale.value}` + `watch: [locale]`, chaine `.where('draft','=',false).order('date','DESC').all()`. - **Filtre JS** : `articles = computed(() => all.filter(a => Array.isArray(a.tags) && a.tags.includes('hytale')).slice(0, 2))` — guard Array.isArray contre TypeError (T-08-01), filtre JS car LIKE SQLite unreliable sur tags[] (D-11). - **Template** : `
` (masque total si vide, D-12), header `// recent-articles` + h2 `text-3xl sm:text-4xl` + subtitle, grille `grid-cols-1 md:grid-cols-2 gap-5 lg:gap-6`, `` v-for, footer NuxtLink `localePath('/blog')` avec icon arrow-right. ### Task 2 — Injection + i18n (commit `bf2ec86`) - `app/pages/hytale.vue` : `` insere apres le wrapper TestimonialsSection, avant fermeture root div. Aucun script change (auto-import Nuxt). - `i18n/locales/fr.json` : nouveau bloc `hytale.recentArticles` sibling de `hero/services/pricing` avec title "Articles récents", subtitle "Les dernières publications sur le développement de plugins Hytale", viewAll "Voir tous les articles" (style accentue aligne `blog.*` 2026). - `i18n/locales/en.json` : miroir EN "Recent articles" / "Latest writing on Hytale plugin development" / "View all articles". ## Verification - `pnpm typecheck` exit 0 (deux passes, apres chaque tache) - `node -e "JSON.parse(...)"` FR + EN OK (pas de trailing comma ni syntax error) - `grep "queryCollection\\('blog_(fr|en)'\\)"` → 2 branches litterales presentes - `grep "v-if=\"articles.length\""` → present - `grep "variant=\"compact\""` → present - `grep "tags.*includes.*'hytale'"` → filtre JS present - `grep "