From fdd7f399729a81301c9b6a3bfe30667430780cdb Mon Sep 17 00:00:00 2001 From: kayjaydee Date: Fri, 10 Apr 2026 18:08:28 +0200 Subject: [PATCH] docs: complete project research Co-Authored-By: Claude Opus 4.6 (1M context) --- .planning/research/ARCHITECTURE.md | 333 ++++++++++++++++++++++++++ .planning/research/FEATURES.md | 368 +++++++++++++++++++++++++++++ .planning/research/PITFALLS.md | 258 ++++++++++++++++++++ .planning/research/STACK.md | 324 +++++++++++++++++++++++++ 4 files changed, 1283 insertions(+) create mode 100644 .planning/research/ARCHITECTURE.md create mode 100644 .planning/research/FEATURES.md create mode 100644 .planning/research/PITFALLS.md create mode 100644 .planning/research/STACK.md diff --git a/.planning/research/ARCHITECTURE.md b/.planning/research/ARCHITECTURE.md new file mode 100644 index 0000000..18e497a --- /dev/null +++ b/.planning/research/ARCHITECTURE.md @@ -0,0 +1,333 @@ +# Architecture Patterns + +**Project:** Portfolio Killian' Dalcin — Nuxt 4 SSR +**Researched:** 2026-04-10 +**Confidence:** HIGH (verified against actual codebase + Nuxt 4 docs patterns) + +--- + +## Is the Current Architecture Sound? + +**Yes — the foundation is solid.** The core SSR pipeline is correctly implemented: + +- `ssr: true` + `compatibilityVersion: 4` — full SSR, not hybrid +- `@nuxtjs/i18n` with `prefix_except_default` — SEO-correct URL scheme (FR at `/`, EN at `/en/`) +- `@nuxtjs/color-mode` with `storage: 'cookie'` — theme class applied server-side, no flash +- `useSeoMeta()` with reactive `() => t('...')` callbacks — meta resolves server-side per locale +- `useLocaleHead()` in `app.vue` — injects `hreflang` alternates on every page automatically +- Data layer (`app/data/*.ts` + `useProjects()`) is clean: static IDs, translated fields via i18n keys, reactive recomputation on locale change + +Three real problems exist in the current implementation (not architecture flaws — just execution gaps): + +1. **og:image hardcoded** to `https://killiandalcin.fr/og-image.png` on all 6 pages, including project detail where `project.image` is already available +2. **JSON-LD only on homepage** — other pages have no structured data; `jobTitle` still says "Developpeur Full Stack Freelance" instead of positioning Hytale +3. **ogUrl missing** — `useSeoMeta()` calls don't include `ogUrl`, so the canonical URL is absent from Open Graph, though `` is provided by `@nuxtjs/i18n` + +--- + +## Recommended Architecture + +The existing layer structure is correct. No refactoring needed. Extensions follow the same pattern: + +``` +Pages (app/pages/) + hytale.vue ← new page, same pattern as fiverr.vue + project/[id].vue ← add dynamic og:image (project.image already there) + +Composables (app/composables/) + useSeoMeta → per-page calls ← add ogUrl to every page + useJsonLd.ts ← new: centralize JSON-LD generation + +Data (app/data/) + hytale.ts ← new: pricing tiers, service cards, tech highlights + site.ts ← update jobTitle to Hytale positioning + +Locales (app/locales/fr.json, en.json) + seo.hytale.* ← new SEO keys + hytale.* ← new page content keys +``` + +### Component Boundaries for the Hytale Page + +| Component | Responsibility | Communicates With | +|-----------|---------------|-------------------| +| `pages/hytale.vue` | Page assembly, SEO, JSON-LD | `HytaleHeroSection`, `HytalePricingGrid`, `HytaleServiceCards`, `FAQSection`, `CTASection` | +| `sections/HytaleHeroSection.vue` | Hero — "Hytale Plugin Developer" headline, early access badge | `useI18n()` | +| `sections/HytalePricingGrid.vue` | 3-column pricing table (Simple / Complex / Sur-mesure + Maintenance) | `app/data/hytale.ts` via props | +| `sections/HytaleServiceCards.vue` | What's included per service, tech stack used | `app/data/hytale.ts` via props | +| Reuse `FAQSection.vue` | Hytale-specific FAQs | `data/faq.ts` (add `hytaleFAQs` export) | +| Reuse `CTASection.vue` | Call to action to contact / Fiverr | props | + +Follow the `fiverr.vue` structural pattern exactly — it already does service cards correctly. The Hytale page is a thematic variant, not a new pattern. + +--- + +## og:image Hardcoding Fix + +**Problem:** All pages including `project/[id].vue` use the same static `og-image.png`. The project detail page already has `project.value?.image` available but ignores it. + +**Fix: per-page og:image strategy** + +```typescript +// pages/project/[id].vue — already has project data, just use it +useSeoMeta({ + // ... + ogImage: () => project.value?.image + ? `https://killiandalcin.fr${project.value.image}` + : 'https://killiandalcin.fr/og-image.png', +}) +``` + +For all other pages, create dedicated OG images rather than sharing one. The naming convention: + +| Page | File | Dimensions | +|------|------|-----------| +| Default / fallback | `/public/og/og-default.png` | 1200×630 | +| Hytale | `/public/og/og-hytale.png` | 1200×630 | +| Fiverr | `/public/og/og-fiverr.png` | 1200×630 | +| Projects | `/public/og/og-projects.png` | 1200×630 | + +Each page's `useSeoMeta()` references its own file. This is the simplest, most reliable approach — no server-side image generation required, works perfectly with SSR, zero dependencies. + +**Do not use `@vercel/og` or `nuxt-og-image`.** The portfolio is Docker-deployed, not Vercel. `nuxt-og-image` adds a Satori/Chromium dependency and requires additional config for non-Vercel deployments. Static pre-made images are sufficient for a portfolio and have zero runtime cost. + +--- + +## Canonical URL Strategy with prefix_except_default + +**Current situation:** `@nuxtjs/i18n` with `prefix_except_default` + `baseUrl: 'https://killiandalcin.fr'` automatically generates: + +```html + + + + + + + + + + +``` + +This is correct — `useLocaleHead()` in `app.vue` handles all of this automatically. **No manual canonical management needed.** + +The one gap is `ogUrl` in Open Graph. Add it to every page's `useSeoMeta()`: + +```typescript +// Pattern for every page +const { locale } = useI18n() +const localePath = useLocalePath() + +useSeoMeta({ + // ... existing fields ... + ogUrl: () => `https://killiandalcin.fr${localePath('/hytale')}`, +}) +``` + +`useLocalePath()` resolves the correct prefixed path for the current locale (`/hytale` for FR, `/en/hytale` for EN), making `ogUrl` SSR-safe and locale-correct. + +**For the sitemap:** `@nuxtjs/sitemap` already reads from `@nuxtjs/i18n` configuration and generates hreflang entries automatically. No manual sitemap management needed for the Hytale page — it appears automatically when `pages/hytale.vue` is created. + +--- + +## JSON-LD Structured Data Patterns + +### What to Use Per Page + +| Page | Schema Types | Priority | +|------|-------------|----------| +| `/` (homepage) | `Person` + `WebSite` + `ProfessionalService` | Exists, needs update | +| `/hytale` | `Service` (×3 tiers) + `SoftwareApplication` | New | +| `/projects` | `ItemList` of `SoftwareSourceCode` | Nice to have | +| `/project/[id]` | `SoftwareSourceCode` or `CreativeWork` | Nice to have | +| `/fiverr` | `Offer` per service | Nice to have | +| `/contact` | `ContactPage` | Low value | + +### Centralize with a Composable + +The current pattern inlines JSON-LD in each page's `useHead()`. This works but leads to duplication of `Person` data across pages. Centralize reusable schemas: + +```typescript +// app/composables/useJsonLd.ts +export function usePersonSchema() { + return { + '@type': 'Person', + name: "Killian' DAL-CIN", + url: 'https://killiandalcin.fr', + jobTitle: 'Hytale Plugin Developer & Full Stack Developer', + email: 'contact@killiandalcin.fr', + sameAs: [ + 'https://linkedin.com/in/killian-dal-cin', + 'https://www.fiverr.com/users/mr_kayjaydee', + 'https://gitea.kamisama.ovh/kayjaydee', + ], + } +} + +export function useWebSiteSchema() { + return { + '@type': 'WebSite', + name: "Killian' DAL-CIN", + url: 'https://killiandalcin.fr', + potentialAction: { + '@type': 'SearchAction', + target: 'https://killiandalcin.fr/projects?q={search_term_string}', + 'query-input': 'required name=search_term_string', + }, + } +} + +export function useHytaleServiceSchemas() { + return [ + { + '@type': 'Service', + name: 'Hytale Plugin Development — Simple', + provider: { '@type': 'Person', name: "Killian' DAL-CIN" }, + serviceType: 'Software Development', + description: 'Basic Hytale plugin: single mechanic, standard features', + offers: { '@type': 'Offer', priceCurrency: 'USD', price: '150' }, + }, + { + '@type': 'Service', + name: 'Hytale Plugin Development — Complex', + provider: { '@type': 'Person', name: "Killian' DAL-CIN" }, + serviceType: 'Software Development', + description: 'Advanced Hytale plugin with custom systems, multiplayer, persistence', + offers: { '@type': 'Offer', priceCurrency: 'USD', price: '400' }, + }, + { + '@type': 'Service', + name: 'Hytale Plugin Maintenance', + provider: { '@type': 'Person', name: "Killian' DAL-CIN" }, + serviceType: 'Software Maintenance', + description: 'Monthly plugin maintenance: update compatibility after Hytale patches', + offers: { '@type': 'Offer', priceCurrency: 'USD', priceSpecification: { '@type': 'UnitPriceSpecification', price: '50', unitCode: 'MON' } }, + }, + ] +} +``` + +Each page then composes what it needs: + +```typescript +// pages/index.vue +const { usePersonSchema, useWebSiteSchema } = useJsonLd() +useHead({ + script: [{ + type: 'application/ld+json', + innerHTML: JSON.stringify({ + '@context': 'https://schema.org', + '@graph': [usePersonSchema(), useWebSiteSchema()], + }), + }], +}) + +// pages/hytale.vue +const { usePersonSchema, useHytaleServiceSchemas } = useJsonLd() +useHead({ + script: [{ + type: 'application/ld+json', + innerHTML: JSON.stringify({ + '@context': 'https://schema.org', + '@graph': [usePersonSchema(), ...useHytaleServiceSchemas()], + }), + }], +}) +``` + +### SoftwareApplication for Hytale Plugins + +For the Hytale page, `SoftwareApplication` is the most SEO-relevant schema for plugin demos or featured work: + +```json +{ + "@type": "SoftwareApplication", + "name": "Hytale Plugin — [Plugin Name]", + "applicationCategory": "GameApplication", + "operatingSystem": "Hytale", + "author": { "@type": "Person", "name": "Killian' DAL-CIN" }, + "offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" } +} +``` + +Use `SoftwareApplication` only when there are real plugin demos or releasable plugins. Use placeholder data with clearly marked demo content for now. + +--- + +## Hytale Page: Pricing Grid Pattern + +The most effective pricing grid for this use case is a 3-tier table with a highlighted middle tier. Nuxt UI v3 provides everything needed without custom components: + +```vue + +
+ + ... + + + + + ... + + + + ... +
+``` + +Pricing data belongs in `app/data/hytale.ts` (not `siteConfig`) because it is Hytale-specific content, not site-wide configuration. Translatable labels live in locale files; prices stay in the data file (they are locale-independent). + +--- + +## Anti-Patterns to Avoid + +### Anti-Pattern 1: Duplicating Person schema across pages as raw objects +**What:** Copy-pasting the full `Person` object in every page file +**Why bad:** When Killian's jobTitle changes to "Hytale Plugin Developer", every page needs updating manually +**Instead:** `useJsonLd.ts` composable as shown above — single source of truth + +### Anti-Pattern 2: Using localStorage for any SSR state +**What:** Storing locale or theme in localStorage +**Why bad:** Causes hydration mismatch — server renders with default, client re-renders after mount +**Instead:** Cookie-only (already correctly implemented) + +### Anti-Pattern 3: Static ogUrl strings +**What:** `ogUrl: 'https://killiandalcin.fr/hytale'` hardcoded +**Why bad:** EN version at `/en/hytale` gets wrong ogUrl, confusing social crawlers +**Instead:** `ogUrl: () => \`https://killiandalcin.fr\${localePath('/hytale')}\`` + +### Anti-Pattern 4: Translating prices +**What:** Putting price strings like "$150" or "150€" in locale files +**Why bad:** Prices change independently of language; mixes content types +**Instead:** Prices in data file, currency/format computed if needed + +### Anti-Pattern 5: nuxt-og-image for a Docker-SSR deployment +**What:** Using Satori-based dynamic OG image generation +**Why bad:** Adds Chromium/Satori dependency, complex config for non-Vercel targets, overhead per request +**Instead:** Static pre-made OG images per page in `/public/og/` + +--- + +## Scalability Considerations + +This is a static-content portfolio. Scalability is not a concern. The architecture is appropriate for: +- ~10 pages +- ~20 projects +- 2 locales +- No user accounts, no dynamic data beyond the contact form + +The only scaling vector is content volume (more projects, more services). The current data layer (`app/data/`) handles this cleanly — add entries to arrays, add i18n keys, done. + +--- + +## Sources + +- Nuxt 4 docs: `ssr: true`, `compatibilityVersion: 4` — verified against current nuxt.config.ts +- `@nuxtjs/i18n` v9 docs: `prefix_except_default`, `useLocaleHead()`, `useLocalePath()` — HIGH confidence +- Schema.org: `Service`, `SoftwareApplication`, `Person`, `WebSite` — HIGH confidence +- `@nuxtjs/sitemap` v6: auto i18n integration — HIGH confidence (verified module is installed) +- Pattern for `useJsonLd.ts` composable: derived from existing codebase conventions (composable-per-concern) +- og:image static file strategy: MEDIUM confidence (sufficient for use case, no dynamic content needed) diff --git a/.planning/research/FEATURES.md b/.planning/research/FEATURES.md new file mode 100644 index 0000000..1605a39 --- /dev/null +++ b/.planning/research/FEATURES.md @@ -0,0 +1,368 @@ +# Feature Landscape + +**Domain:** Freelancer portfolio — niche game plugin developer (Hytale) +**Researched:** 2026-04-10 +**Confidence:** MEDIUM — based on codebase analysis, domain knowledge, freelance market patterns; WebSearch unavailable + +--- + +## 1. Freelancer Portfolio Pricing Pages — Visible vs Hidden + +### Verdict: Show pricing. Always. + +**Rationale for Killian's situation specifically:** + +The Hytale plugin dev market on Fiverr has ~1 direct competitor at $45. The market is not price-sensitive yet — it's trust-sensitive. A server owner searching "Hytale plugin developer" has no reference price. Showing prices: +- Filters unserious inquiries before they consume calendar time (critical with only 5-10h/week availability) +- Signals confidence and professionalism +- Anchors expectations upward (a visible €300 tier makes a €100 tier feel reasonable) +- Removes the "I need to ask" friction that kills conversions for international clients in different timezones + +**The only valid reason to hide pricing:** Custom enterprise work where scope varies by 10x. That does not apply here — plugin complexity is bounded. + +### Recommended Tier Structure + +Three tiers work best for plugin dev services. Four or more creates decision paralysis. + +| Tier | Name | Price Range | Contents | +|------|------|-------------|----------| +| Starter | Simple Plugin | €80–150 | Single feature, documented, delivered in 5 days, 15 days support | +| Standard | Complex Plugin | €200–400 | Multiple systems (economy, progression, custom events), 30 days support, 1 revision round | +| Premium | Full Experience | €500–900 | Full game loop (dungeon, boss, economy, UI), architecture doc, maintenance contract option | +| Recurring | Maintenance | €30–60/mo | Compatibility updates per Hytale version, bug fixes, 1 minor feature/month | + +**Key structural decisions:** + +- Put the maintenance tier visually separate — it is a different product (recurring revenue vs one-shot) +- "Starting at" language is fine for custom tier, but anchor with a concrete base price +- Show what is explicitly NOT included (server hosting, assets/textures, art) — this prevents scope creep complaints +- Add a "Most Popular" badge on Standard. It normalizes the mid tier and lifts average order value. + +**Nuxt UI v3 implementation:** Use `UCard` grid (3 columns desktop, 1 column mobile). The pricing tiers do not need a dedicated library — straight Tailwind + UCard is sufficient. Avoid installing a pricing-specific component library. + +--- + +## 2. Hytale Plugin Services Page — What Server Owners Need to See + +### The buyer persona + +A Hytale server owner is typically: +- Non-technical (they run a server, they don't code it) +- Risk-averse (bad plugin = server downtime = player churn) +- Skeptical ("can you even build Hytale plugins, the game just launched") +- Looking for long-term relationship, not one-shot delivery + +They have Minecraft server experience and will compare to that ecosystem. Key questions in their head: + +1. Does this dev actually know Hytale specifically, or will they fake it? +2. What happens when the next Hytale update breaks my plugin? +3. Can I see examples or a demo? +4. Will they be around in 6 months? + +### Page sections — recommended order + +**Section 1: Credibility header** +- Title: "Hytale Plugin Developer" — not "Game Dev" or "Modder" +- One-liner that addresses skepticism: "Building for Hytale since Early Access — I track every API change so your server stays running" +- Availability badge (reuse the animated one from HeroSection) + +**Section 2: What makes Hytale plugins different** +- Short educational paragraph (3-4 sentences) explaining the Hytale API vs Minecraft — this signals genuine knowledge +- Mention: Hytale uses a Java/Kotlin API, the modding system is fundamentally different from Spigot/Paper, requires adapting to active API evolution +- This signals to server owners that this developer is not a Minecraft dev pretending + +**Section 3: Services grid (the pricing section described above)** +- Four cards: Simple, Complex, Full Experience, Maintenance +- Each card must answer: What do I get? How long? What's the support situation? + +**Section 4: The maintenance pitch — this is the unique selling point** +- Dedicated callout/alert component (UAlert or custom banner) +- Message: Hytale updates frequently during Early Access. Every major update risks breaking plugins. A maintenance contract means zero downtime and no re-negotiation on every patch. +- This is the structural advantage from PROJECT.md — lean into it hard + +**Section 5: Process (3-step)** +- Step 1: Discovery call (Discord preferred — server owners are on Discord) +- Step 2: Spec + quote in 48h +- Step 3: Delivery with documentation +- Keep this extremely short — server owners don't read walls of text + +**Section 6: Demo / Portfolio** +- If no Hytale projects exist yet: use Minecraft plugin work as proof of concept + explicit note "Hytale API is similar to Java/Kotlin modding I've done for Minecraft — I'm actively building Hytale demos" +- A "coming soon" placeholder is better than no section — it signals intent +- Video embed or GIF of a plugin in action converts better than screenshots + +**Section 7: FAQ specific to Hytale** +- "The game is in Early Access, is this risky?" — address directly +- "What if Hytale updates break my plugin?" — maintenance contract answer +- "Do you have experience with Hytale specifically?" — honest answer + Minecraft parallel +- "Can I pay per update?" — redirect to maintenance tier + +**Section 8: CTA** +- Primary: "Book a Discovery Call" (Discord link or contact form) +- Secondary: "View Pricing" + +### Route: `/hytale` (not `/games` or `/modding`) + +The URL slug matters for SEO. `/hytale` captures "hytale plugin developer" searches directly. Register `hytale` in the nav alongside the existing pages. + +--- + +## 3. Testimonials Section — Displaying 5–10 Reviews + +### Current state + +`testimonials.ts` has 5 real reviews, all 5-star, all from Fiverr. All French-language except one English ("awesome guy"). `testimonialsStats` declares 10 total reviews and 25 projects — slightly inflated vs actual data shown. + +### The small-count problem + +5 reviews is not a weakness if framed correctly. The mistake is showing 5 cards and letting the sparseness speak for itself. Solutions: + +**Pattern 1: Featured + Grid (recommended)** +- 1 large "featured" testimonial card (the unqlf_ one — it's the most specific and includes project type "Plugin Minecraft") +- 4 smaller cards below in 2x2 grid +- This asymmetric layout fills the space and makes 5 cards look curated rather than scarce +- The `featured: true` flag is already on the right testimonial in the data + +**Pattern 2: Carousel with autoplay** +- Works for mobile; hides the count +- Risk: autoplay is annoying and reduces trust +- Not recommended + +**Pattern 3: Stats bar above cards** +- "5.0 / 5.0 — 10+ verified reviews on Fiverr" + link to Fiverr profile +- This shifts the authority to a third-party platform — more credible than displaying 5 internal cards +- Add a Fiverr logo/icon next to the stat to reinforce the source +- The `reviewsLink` already exists in i18n pointing to the Fiverr profile + +**Pattern 4: Language split — show the English one on the EN locale** +- The "awesome guy" testimonial (botuhuh) is English and international — feature it prominently on the EN locale +- French testimonials go first on FR locale +- This can be implemented by sorting testimonials client-side by language match — simple logic in the component + +### Recommended implementation + +Use Pattern 1 + Pattern 3 combined: +- Stats bar (5.0 rating, link to Fiverr) at top +- Featured card (full-width or 60% width) +- 4-card 2x2 grid +- "See all reviews on Fiverr" CTA link at bottom + +For the Hytale page specifically, filter testimonials to show only `project_type === 'Plugin Minecraft'` — only 1 exists currently, but it's the most relevant. Pad with a "Review coming soon" placeholder card until more Hytale reviews accumulate. + +### What to fix in the data + +The `results` field is weak — values like `"Prix: Jusqu'à 50€"` and `"Durée: 10 jours"` reveal order size which may underposition the service. Consider removing the price from results display or changing it to outcome-focused language: "Delivered 2 days early", "Still using the plugin 6 months later". + +--- + +## 4. Hero Section — Niche Positioning "Hytale Plugin Developer" + +### Current problem (confirmed by reading the code) + +The hero title uses `t('home.title')` which resolves to "Expert Full Stack Developer for Hire | Vue.js, React & Node.js Specialist" in EN. The code splits the last two words for gradient styling — this technique only works if the last two words are the differentiating concept (they're not: "Node.js Specialist" gets the gradient). + +Additionally, the terminal code block hardcodes `'Full Stack Dev'` as the role. The terminal does show `'Hytale Plugins'` in the skills array — good — but it is buried among 6 other skills. + +The availability badge says "Available for projects" — hardcoded English in the template (not using i18n key), which is a bug. + +### Hero best practices for niche positioning + +**Rule 1: Specialization in H1, not in paragraph** +The H1 must state the specialization. Visitors scan H1, they don't read paragraphs. "Hytale Plugin Developer" must be in the H1, not in the subtitle or skills list. + +**Rule 2: Acknowledge the broader skill set in subtitle, not in title** +The subtitle is the right place to mention Vue/Node/web work — it reassures server owners that this is a real professional developer, not a hobbyist. + +**Rule 3: Dual-audience heading (Hytale + Web)** +Killian has two buyer types: Hytale server owners and web clients. The hero must serve the primary audience (Hytale — the strategic bet) without completely alienating web clients. + +Recommended approach: tabbed or split hero is overkill. Use a primary H1 that leads with Hytale, with a secondary descriptor: + +``` +Hytale Plugin Developer +& Freelance Web Dev +``` + +The gradient goes on "Hytale Plugin Developer". Web services are the secondary line. + +**Rule 4: Specificity = trust** +"I build custom Hytale plugins that survive every API update" beats "I build custom solutions that scale." + +**Rule 5: Terminal widget — update the role** +Change `'Full Stack Dev'` to `'Hytale Plugin Dev'` in HeroSection.vue. This is a hardcoded string in the template (line 104), not using i18n, so fix it directly. + +### i18n changes required in hero + +The `home.title` split-by-last-2-words approach is fragile — it produces different results for FR and EN because sentence structure differs. The right solution: + +- Split `home.title` into two keys: `home.title.main` and `home.title.highlight` +- `home.title.highlight` gets the gradient styling +- This removes the brittle `split(' ').slice(-2)` logic + +New i18n values: + +```json +// EN +"home": { + "title": { + "main": "Hytale Plugin Developer", + "highlight": "& Freelance Web Dev" + }, + "subtitle": "I build custom Hytale plugins that survive every API update — and web apps that convert. 7+ years of experience, 0 missed deadlines." +} + +// FR +"home": { + "title": { + "main": "Développeur de Plugins Hytale", + "highlight": "& Dev Web Freelance" + }, + "subtitle": "Je construis des plugins Hytale qui survivent à chaque mise à jour de l'API — et des applications web qui convertissent. 7+ ans d'expérience, 0 délais manqués." +} +``` + +The availability badge text ("Available for projects") is hardcoded in HeroSection.vue line 30 — needs to be an i18n key `home.availableBadge`. + +--- + +## 5. i18n Audit — Finding Missing and Bad Translations + +### Issues already visible in the current files + +**Structural parity issues (EN has keys FR is missing or vice versa):** +- Both files have identical key structure currently — no missing keys found at top level +- Risk area: as new pages (Hytale) and features (pricing) are added, keys will diverge + +**Quality issues in existing translations:** + +EN quality problems: +- `home.title` = "Expert Full Stack Developer for Hire | Vue.js, React & Node.js Specialist" — generic SEO-spam tone, not the niche positioning needed +- `seo.home.title` still says "Freelance Full Stack Developer" — must change to include Hytale +- `seo.home.description` makes no mention of Hytale, plugins, or game development +- `a11y.logoLabel` = "Full Stack Developer" — must update when hero positioning changes +- `footer.servicesList` contains "Mobile Apps" and "Tech Consulting" — neither is a real service offered +- `fiverr.subtitle` claims "500+ orders delivered" and "100% satisfaction rate" — verify these are accurate; if not, this is a credibility risk + +FR quality problems: +- `about.title` = "À propos de Killian'- Développeur Full Stack" — the dash is missing a space before it (`Killian'-` should be `Killian' —` or just remove) +- `faq.homeFaq.delivery.answer` mentions "Bot Discord simple" as the first example — for a Hytale-focused portfolio this should lead with Hytale plugin timelines +- `contact.methods.availability` = "Disponible pour remote & freelance" — the English word "remote" in French copy feels lazy; use "télétravail" + +Hardcoded strings (not using i18n at all): +- HeroSection.vue line 30: `"Available for projects"` — hardcoded EN +- HeroSection.vue line 104: `'Full Stack Dev'` — hardcoded in terminal widget +- HeroSection.vue line 148: `"50+ projects"` — hardcoded EN +- HeroSection.vue line 152: `"5.0 rating"` — hardcoded EN + +### Audit methodology for ongoing use + +**Method 1: Key extraction diff (best for structural parity)** + +```bash +# Extract all keys from both files and diff +node -e " + const en = require('./i18n/locales/en.json'); + const fr = require('./i18n/locales/fr.json'); + const flatten = (obj, prefix='') => Object.keys(obj).reduce((acc, k) => { + const key = prefix ? prefix + '.' + k : k; + return typeof obj[k] === 'object' && !Array.isArray(obj[k]) + ? { ...acc, ...flatten(obj[k], key) } + : { ...acc, [key]: obj[k] }; + }, {}); + const enKeys = Object.keys(flatten(en)); + const frKeys = Object.keys(flatten(fr)); + const missingInFr = enKeys.filter(k => !frKeys.includes(k)); + const missingInEn = frKeys.filter(k => !enKeys.includes(k)); + console.log('Missing in FR:', missingInFr); + console.log('Missing in EN:', missingInEn); +" +``` + +Run this after every feature addition that adds i18n keys. + +**Method 2: Search for hardcoded strings in templates** + +```bash +# Find text content in templates that bypasses t() +grep -rn '>[A-Z][a-z]' app/components/ app/pages/ | grep -v '{{' | grep -v 't(' | grep -v ':' | grep -v '