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:

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:

  1. SCP gtranslate-nav-placement.php to wp-content/mu-plugins/ on production
  2. Install + activate gtranslate plugin on production (same free version)
  3. Configure GTranslate options on production to match local (en source, es target, no widgets)
  4. Commit + deploy the gsap-animations.js change through the usual theme deploy path
  5. Verify on a few pages
  6. Cache purge (Cloudflare / Flywheel)

Per CLAUDE.md hard rule: each prod action needs explicit per-action approval before running.

Open items / next steps