Spanish Toggle Implementation — Main CF (May 7, 2026)
Companion doc to ../../solano-site/2026-05-07-spanish-toggle-implementation.md. Read that one first — it's the canonical playbook. This one only documents the differences.
What landed
A working Spanish ↔ English toggle on the local main CF site:
- Desktop: last item inside
#site-navigation's<ul>, flush with the existing nav links (between "About" and the "Join the call to break ground" CTA) - Mobile: top of
#mobile-menu-overlay, above the nav links, with a subtle divider below
Same flag-pair visual as Solano: 🇺🇸 EN / 🇲🇽 ES, opacity-only highlight on active. Color follows the header style — cream on navy/transparent, navy on beige/sand/linen.
Status: Implemented locally. Verified translation works both directions. Not yet pushed to production.
Files
| Path | What it is | Tracked |
|---|---|---|
~/Local Sites/californiaforever/app/public/wp-content/mu-plugins/gtranslate-nav-placement.php |
Stop-gap mu-plugin. Server-side injection, CSS, JS hydration. | No (mu-plugins/ is host-managed) |
~/Local Sites/californiaforever/app/public/wp-content/plugins/gtranslate/ |
GTranslate plugin (copied from Solano local). Activated. | No (plugins are host-managed) |
~/Local Sites/californiaforever/app/public/wp-content/themes/california-forever/js/gsap-animations.js |
Modified: added GT_TRANSLATED cookie check; both SplitText calls now skip per-letter splits when in non-English mode |
Yes (will need a commit) |
Differences from Solano
1. Different insertion anchors
Solano injected the toggle as a sibling inside #grouped-nav (a Plumb-era nav element). Main CF uses #site-navigation with a <ul> of <li> items, so we inject as a new <li class="header-nav-item gt-flag-toggle-item"> at the end of the list. That makes the toggle inherit the <ul>'s gap utilities and align naturally with the other nav items.
For mobile, Solano uses an mmenu drawer with cloned items; main CF has its own #mobile-menu-overlay with .mobile-menu-nav. We inject the toggle as a sibling div ABOVE the <ul>, with a thin border-bottom and 16px breathing room.
2. Color-aware via data-header-style
Main CF's header has data-header-style="navy|beige|transparent|sand|linen". The toggle's text color is set in CSS via attribute selectors so it inherits the right palette automatically — cream (#FDF9E3) on navy/transparent, navy (#112D40) on beige/sand/linen. The flag emoji (rendered as <img class="emoji"> by WP) is unaffected.
3. No .container padding override
Solano needed a header/alert-bar padding override because the existing .container class capped at 1440px and the new toggle squished things. Main CF's nav has its own breathing logic (.h-[77px] flex items-center) and the toggle fits without that hack.
4. Inline GTranslate scaffolding
GTranslate plugin only enqueues its JS (and inserts #google_translate_element2 + defines doGTranslate / googleTranslateElementInit2) when a widget is rendered — via shortcode, sidebar widget, or a WP nav-menu item with the menu-item-gtranslate class.
Solano has WP nav-menu items with that class (legacy, in the utility nav), so the plugin auto-mounts the scaffolding and Solano's mu-plugin only needs to hide the visible UI.
Main CF has none of those. So the mu-plugin inlines the scaffolding directly: the #google_translate_element2 div, window.doGTranslate, and window.googleTranslateElementInit2 are defined verbatim from plugins/gtranslate/js/dropdown.js. The lazy element.js loader and cookie-aware auto-load logic are unchanged from Solano.
5. SplitText / GSAP letter animations had to be made translation-aware
This was the gotcha. The CF theme uses GSAP SplitText to wrap each letter of hero headlines in its own <div> for character-by-character reveal. Google Translate then sees each letter as an independent translation unit and produces gibberish ("Building" → "Btúildinortegramo" because "n"→"norte", "g"→"gramo", etc).
Fix: added GT_TRANSLATED = /googtrans=\/[a-z]+\/(?!en\b)/.test(document.cookie) at the top of gsap-animations.js. Both SplitText invocations (.split-animate-immediate and the front-page hero h1) check this flag and, when in non-English mode, fade the heading as a single block instead of splitting it. English users see the original character-by-character reveal; Spanish users see clean translated text with a simpler fade.
6. Toggle click reloads instead of live-translating
For the same SplitText reason: if the user toggled live (doGTranslate('en|es') without reload), the heading's letters are already split into <div>s from the initial English DOMContentLoaded run, and Google Translate would still see per-letter units.
So the toggle click handler now sets/clears the googtrans cookie and calls location.reload(). On the fresh load, gsap-animations.js sees the cookie and skips the split, and Google Translate's element.js auto-loads (per the cookie-detection branch we already had) and translates clean text.
Trade-off: a full page reload on every toggle (vs. a live in-place translation). For a site with letter-split animations, this is the right call.
Production push
Same as Solano workflow, plus the theme commit:
- SCP
gtranslate-nav-placement.phptowp-content/mu-plugins/on production - Install + activate
gtranslateplugin on production (same free version) - Configure GTranslate options on production to match local (en source, es target, no widgets)
- Commit + deploy the
gsap-animations.jschange through the usual theme deploy path - Verify on a few pages
- Cache purge (Cloudflare / Flywheel)
Per CLAUDE.md hard rule: each prod action needs explicit per-action approval before running.
Open items / next steps
- Production push — pending Eric's go.
- Solano production push still queued separately (per the May 7 Solano implementation note).
- Track B (custom AI translation workflow) — still parked pending Anders.
- Other pages — only smoke-tested the homepage. Worth clicking through
/break-ground-now/,/economic-impact/, the FAQ archive, and the Phase II content before declaring it solid.