Skip to content

Indexable Hungarian and Spanish pages with hreflang (SEO spec 3) - #236

Merged
Programcode merged 4 commits into
mainfrom
seo/hu-es-pages
Oct 26, 2026
Merged

Programcode merged 4 commits into
mainfrom
seo/hu-es-pages

Conversation

@Programcode

@Programcode Programcode commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

This is Spec 3 of the SEO sprint (seo-sprint/cody-specs.md). It makes Hungarian and Spanish pages indexable and adds hreflang.

Adam (native Hungarian speaker) reviews the Hungarian copy before any merge. The copy was written in Hungarian rather than translated word for word, but it still needs a native read. Do not merge until that review is done. The Spanish copy should get a read too. The list of terms to check is at the end.

What changes

  • /hu and /es are real pages now. vercel.json no longer has the 302 from /:lang(es|hu) to /?lang=. The build prerenders the landing page and the five free tool pages in Hungarian and Spanish. Each one gets <html lang>, its own title, description, H1 and canonical, and its own JSON-LD (WebPage/WebApplication with inLanguage, plus a localized FAQPage that matches the visible FAQ).
  • Slugs. The slugs are localized, as the spec suggested.
    • HU: /hu/tools, /hu/tools/vitaidomero, /hu/tools/vitatetel-generator, /hu/tools/jegyzetlap, /hu/tools/beszedido-kalkulator. These were renamed after review (see below); none was ever live, so they need no redirects.
    • ES: /es/tools, /es/tools/cronometro-debate, /es/tools/generador-mociones, /es/tools/hoja-de-flujo, /es/tools/calculadora-tiempo-discurso
    • src/config/localized-routes.json is the one list. The router, route loaders, prerender, check-site and the llms.txt generator all read it. Documented in docs/operations.md.
  • hreflang. Every page in a trio (home, tools hub, each tool) carries en, hu, es and x-default (English) in the head. sitemap.xml repeats them as xhtml:link, read from each page's own head so there is only one source. Pages without equivalents get none.
  • New build check. check-site now fails on hreflang that doesn't resolve, leaves out the page itself or x-default, disagrees with <html lang>, or isn't the same set on every page it names.
  • The language switcher still works.
    • Opening /hu chooses Hungarian the same way ?lang=hu does, so Start leads into the app in Hungarian.
    • Picking another language on /hu moves to that language's address, /es or /.
    • After the app takes over, the canonical stays /hu instead of flipping back to /. A unit test covers this; it fails without the fix.
  • Facts come from site.json. The trial length and Pro price on the landing and tool pages were hard-coded "3" and "$5" in all three catalogs. They now read {{trialDays}} and {{proMonthly}}, which i18next fills from site.json. termsPage.p4 (legal text) is unchanged.
  • Old ?lang= homepage links. A homepage-only 301 in vercel.json sends /?lang=hu and /?lang=es to /hu and /es, so those URLs stop showing up as alternates of /. Vercel appends the original query to redirect destinations (production shows /hu?x=1 going to /?x=1&lang=hu), and plain redirects have no option to drop it. The real target is therefore /hu?lang=hu. That is harmless: /hu is canonical to itself, and the client removes ?lang= from the address on load. ?lang=en, unknown values and every other path (/app?lang=hu, /login?lang=hu) are not redirected. A case in vercel-deployment-config.test.ts covers this.
  • llms.txt has new "Magyar" and "Español" sections. The generate_seo_content.py change is one function plus one template line, kept away from internal_links().
  • Files that DebateOwl visual restyle: tokens, fonts, tactile buttons and the owl #228 also touches. Footer.tsx and ShortAnswer.tsx change only their /tools link to the localized address (3 lines each). Hero and Navbar are untouched.

Content limits on the translated tool pages

  • The tool widgets reuse the shared format data, so speech labels such as "A Const", "Team A" and "School 4-6-6-3" stay English. The English page already shows them that way to Hungarian and Spanish users.
  • The 300 practice motions are English only. The HU and ES generator pages say so in the intro, the FAQ and the short answer. The localized pages leave out the full motion list, the embed section and the English format and word-target tables.
  • The Hungarian timer page lists speech times by hand. I checked them against formats.ts, but they won't update if that file changes.
  • I skipped the optional native guides ("Hogyan készülj fel egy vitára" and "Cómo preparar un debate académico").

For Spec 1 (seo/internal-links-brand-entity)

Spec 1's guard test isn't on main yet, so this PR doesn't add one. To make it language-aware:

  • Count inbound links per language. A /hu/... page should need ≥3 inbound links from /hu/... pages, with the English home also counting.
  • Today each translated tool page gets links from its hub, its three sibling tools (OtherTools) and its breadcrumb. /hu and /es get links from every translated tool page's header and breadcrumb.
  • The homepage GuideHub renders on /hu and /es too. It needs a decision there: keep the links to English guides, or hide the hub on translated pages.
  • The "≥15 distinct sitemap URLs" check should apply to / only.

Merged main (92d7113a: #233, #235, #234)

git merge origin/main at 6d20371d. There was no rebase and no force-push.

Review follow-up (seo-sprint/pr-review-233-236.md)

Slugs renamed before anything is indexed: vita-idozito became vitaidomero, vitatema-generator became vitatetel-generator, and flow-lap became jegyzetlap. Links, sitemap, hreflang and llms.txt follow from localized-routes.json.

All 19 Hungarian fixes from the review are applied:

  1. timer FAQ: "Elvész a forduló…"
  2. hub CTA: "nem csak időmérőre"
  3. protected-minute drill: "a bevezetésnél tartasz"
  4. veto drill and the three-motion FAQ: "kizár (megvétóz) egyet"
  5. motion-source FAQ: "az adott tételt"
  6. school-use FAQ: "nem irányul vallás, etnikai csoport vagy élő személy ellen"
  7. timer steps: "a jobb nyílbillentyűvel", "a bal és a jobb nyílbillentyű"
  8. timer intro: "ebben a böngészőlapon"
  9. shortcuts: "nem kapcsol ki a képernyő"
  10. school format: "első cáfolat (az időmérőn R1), második cáfolat (R2) és záróbeszéd". The timer buttons read "Pro R1"/"Pro R2", so the short label stays in brackets to match them.
  11. POI ladder: "percenként"
  12. prep drill: "nem élik fel"
  13. pacing drill: "mint bármelyik tempó önmagában"
  14. cutting steps: "semmit nem adnak hozzá"
  15. calculator CTA: "a forduló végén értékelést is kapsz"
  16. flow-sheet CTA: split into two sentences
  17. flow FAQ: "a bírói döntéshez szükséges jegyzeteknek"
  18. /hu title: "vitafelkészülés"
  19. /hu/tools title: "vitatételek"

Following the review's "pick one term" for fix 19, the tools-hub description and the motion-generator title and description in hu.json pageHead also say "vitatétel" now. The rest of the app's Hungarian UI still says "indítvány"; changing that is a separate decision.

Still open for Adam: POI as "közbekérdezés" or "közbeszólás", "felkészülési keret" or "felkészülési időkeret", and "eset" for case. These are terminology choices from the review, not errors.

Acceptance evidence

Curl against the built dist/, served locally the way Vercel serves it with trailingSlash: false (/x serves x/index.html). vite preview can't do this, because it falls back to the landing page for every path without a trailing slash, /tools included. Only a deployment can prove the redirect is gone from Vercel, and I made no preview deployment.

$ curl -s https://debate.pi-stack.org/hu
HTTP 200
<html lang="hu">
<title>DebateLab – MI-alapú vitafelkészülés és gyakorlás diákoknak</title>
<link rel="canonical" href="https://debate.pi-stack.org/hu" />
<link rel="alternate" hreflang="en" href="https://debate.pi-stack.org/" />
<link rel="alternate" hreflang="hu" href="https://debate.pi-stack.org/hu" />
<link rel="alternate" hreflang="es" href="https://debate.pi-stack.org/es" />
<link rel="alternate" hreflang="x-default" href="https://debate.pi-stack.org/" />

$ curl -s https://debate.pi-stack.org/es
HTTP 200
<html lang="es">
<title>DebateLab – Preparación y práctica de debate con IA para estudiantes</title>
<link rel="canonical" href="https://debate.pi-stack.org/es" />
<link rel="alternate" hreflang="en" href="https://debate.pi-stack.org/" />
<link rel="alternate" hreflang="hu" href="https://debate.pi-stack.org/hu" />
<link rel="alternate" hreflang="es" href="https://debate.pi-stack.org/es" />
<link rel="alternate" hreflang="x-default" href="https://debate.pi-stack.org/" />

$ curl -s https://debate.pi-stack.org/hu/tools/vitaidomero
HTTP 200
<html lang="hu">
<title>Ingyenes vitaidőmérő PF, LD, Policy, BP és World Schools formátumhoz | DebateLab by Pi-Stack</title>
<link rel="canonical" href="https://debate.pi-stack.org/hu/tools/vitaidomero" />
<link rel="alternate" hreflang="en" href="https://debate.pi-stack.org/tools/debate-timer" />
<link rel="alternate" hreflang="hu" href="https://debate.pi-stack.org/hu/tools/vitaidomero" />
<link rel="alternate" hreflang="es" href="https://debate.pi-stack.org/es/tools/cronometro-debate" />
<link rel="alternate" hreflang="x-default" href="https://debate.pi-stack.org/tools/debate-timer" />

$ curl -s https://debate.pi-stack.org/es/tools/cronometro-debate
HTTP 200
<html lang="es">
<title>Cronómetro de debate gratis para PF, LD, Policy, BP y World Schools | DebateLab by Pi-Stack</title>
<link rel="canonical" href="https://debate.pi-stack.org/es/tools/cronometro-debate" />
<link rel="alternate" hreflang="en" href="https://debate.pi-stack.org/tools/debate-timer" />
<link rel="alternate" hreflang="hu" href="https://debate.pi-stack.org/hu/tools/vitaidomero" />
<link rel="alternate" hreflang="es" href="https://debate.pi-stack.org/es/tools/cronometro-debate" />
<link rel="alternate" hreflang="x-default" href="https://debate.pi-stack.org/tools/debate-timer" />

sitemap.xml has 57 URLs (45 before), 18 of them with hreflang. The new /hu and /es entries carry the English landing page's lastmod (2026-10-23), because the build dates the landing page by its last source commit and src/locales/ isn't in that list. That date is earlier than the pages but harmless.

The ?lang= redirect, on the same local server, which applies vercel.json redirects with has conditions and appends the original query the way production does. vercel dev doesn't support has locally, so only a deployment proves the real behaviour.

$ curl -sI 'https://debate.pi-stack.org/?lang=hu'
HTTP/1.1 301 Moved Permanently
location: /hu?lang=hu
$ curl -sI 'https://debate.pi-stack.org/?lang=es'
HTTP/1.1 301 Moved Permanently
location: /es?lang=es
$ curl -sI 'https://debate.pi-stack.org/?lang=en'
HTTP/1.1 200 OK
$ curl -sI 'https://debate.pi-stack.org/app?lang=hu'
HTTP/1.1 200 OK

In a browser, /?lang=hu followed the 301 to /hu?lang=hu, and the client then showed /hu with lang=hu and canonical /hu. /?lang=es&utm_source=x ended on /es?utm_source=x. The switcher on /hu → English went to /, and a reload stayed there with no loop. /app?lang=hu was not redirected and opened sign-in in Hungarian.

In a real browser on the same server, after the app took over:

  • /hu kept lang=hu and canonical /hu; /es kept lang=es and canonical /es. No page errors.
  • Start ("Próba indítása") on /hu went to /login with lang=hu and the title "Bejelentkezés a DebateLabba".
  • Choosing Español in the switcher on /hu moved to /es.
Light Dark
Screenshots are from the first commit (ef88cddc); the review fixes don't change the /hu and /es landing pages except the /hu title.

| /hu phone | | |
| /hu desktop | | |
| /es phone | | |
| /es desktop | | |

Start from /hu landing on sign-in in Hungarian:

Verification

GitHub Actions is billing-blocked, so these are local results, not CI. npm run verify passed at ef88cddc, again after the ?lang redirect (b1aa06a5), and again at the current head fe00edc5. The browser checks below ran at ef88cddc.

  • npm run verify passed (exit 0): docs check, lint, typecheck, 651 unit tests (652 from b1aa06a5), 30 collaboration tests, 482 edge tests, build (prerender, sitemap, check-site, bundle boundaries), service-worker test, and 47 @smoke e2e tests on desktop Chromium.
  • e2e/localization.spec.ts and e2e/free-tools.spec.ts on desktop and mobile Chromium: 36 passed.
  • public-pages, hungarian-parity, startup-shell and home-navigation on desktop and mobile Chromium: 56 passed.
  • I didn't run the full verify:full suite. Three failures already known on main live there: loading-performance.spec.ts:63, navigation-performance.spec.ts:344 and search-quality.spec.ts:7.
  • verify-debatelab recipe recipes/localization (updated here) driven against local Auth, Postgres and Edge Functions (fake model mode): 5/5 on desktop-chromium and 5/5 on mobile-chromium, including the new loc-links check that Start from /hu reaches the app in Hungarian.
  • Nothing was deployed. No production or staging change.

Terms to review

Hungarian (Adam):

  • Flow sheet. The pages say jegyzetlap. Crossfire, grand crossfire and final focus are left in English.
  • App features. Vitapárbaj, Folyamatábra, Esetgenerátor and Ötletelés, taken from the UI catalog.
  • Other debate terms. felkészülési keret, védett idő, türelmi idő, válaszbeszéd.

Spanish:

  • «Esta Casa» / "Resolved"; "preguntas cruzadas" for crossfire and CX; "llevar el flujo" for flowing; "Primer Ministro" for the BP PM.
  • The app itself names the argument flowchart three ways (Diagrama, Diagrama de flujo, Diagramas de argumentos).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Spanish and Hungarian landing pages and debate tools are now available at their own localized URLs.
    • Language switching and navigation keep visitors on the corresponding localized page.
    • Pricing and trial details now reflect the configured product information.
    • Localized pages are included in sitemap and search-discovery content, with language alternates.
  • Bug Fixes
    • Legacy homepage links with a Spanish or Hungarian language parameter now redirect to the matching localized homepage.
    • Localized pages retain their language and canonical address when opened or reloaded.

/hu and /es used to 302 to /?lang=, which served the English HTML with
canonical /. No Hungarian or Spanish page could be indexed.

Now the landing page and the five free tool pages are prerendered in
Hungarian and Spanish at their own addresses (/hu, /es,
/hu/tools/vita-idozito, /es/tools/cronometro-debate, ...). Each has
<html lang>, a self-canonical, and hreflang for en, hu, es and
x-default. The sitemap carries the same alternates as xhtml:link, and
check-site fails the build on hreflang that is one-sided or disagrees
with <html lang>. src/config/localized-routes.json is the one list of
addresses.

Opening a translated address chooses its language like a ?lang= link,
so Start leads into the app in that language. Picking another language
there moves to that language's address. The client keeps the canonical
on the translated address after it takes over.

Trial length and Pro price on these pages now come from site.json
through i18next default variables, in all three languages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Caution

Review failed

The pull request is closed.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 92d7113 and 6d20371.

Files selected for processing: 41
  • .claude/skills/verify-debatelab/features/README.md
  • .claude/skills/verify-debatelab/features/localization.md
  • .claude/skills/verify-debatelab/recipes/localization.spec.ts
  • docs/operations.md
  • e2e/localization.spec.ts
  • public/llms.txt
  • scripts/check-site.mjs
  • scripts/generate_seo_content.py
  • scripts/prerender.mjs
  • scripts/sitemap.mjs
  • src/App.tsx
  • src/components/LocalizedRoute.tsx
  • src/components/free-tools/ToolPageLayout.tsx
  • src/components/landing/Footer.tsx
  • src/components/landing/GuideHub.tsx
  • src/components/landing/ShortAnswer.tsx
  • src/config/localized-routes.json
  • src/i18n.ts
  • src/lib/route-modules.ts
  • src/lib/seo/localized-routes.ts
  • src/lib/seo/tool-pages.ts
  • src/lib/seo/use-page-head.ts
  • src/lib/url-language.ts
  • src/locales/en.json
  • src/locales/es.json
  • src/locales/hu.json
  • src/locales/tools/en.json
  • src/locales/tools/es.json
  • src/locales/tools/hu.json
  • src/main.tsx
  • src/pages/tools/localized/LocalizedToolPages.tsx
  • src/pages/tools/localized/copy-es.ts
  • src/pages/tools/localized/copy-hu.ts
  • src/pages/tools/localized/copy-types.ts
  • src/pages/tools/localized/es.tsx
  • src/pages/tools/localized/hu.tsx
  • src/pages/tools/localized/pages.ts
  • src/prerender/entry.tsx
  • src/test/seo/page-head.test.tsx
  • src/test/vercel-deployment-config.test.ts
  • vercel.json

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

The application now serves Hungarian and Spanish landing and tool pages at localized URLs. Route-aware navigation and language selection use those URLs. Prerendering, page metadata, sitemaps, and generated llms.txt content include localized information and hreflang links.

Changes: Localized Hungarian and Spanish Pages

Layer / File(s) Summary
Localized route registration and language selection
src/config/localized-routes.json, src/lib/seo/localized-routes.ts, src/lib/url-language.ts, src/main.tsx, src/lib/route-modules.ts, src/App.tsx, src/components/LocalizedRoute.tsx, vercel.json, src/test/vercel-deployment-config.test.ts, e2e/localization.spec.ts, .claude/skills/verify-debatelab/..., docs/operations.md
Hungarian and Spanish paths map to page loaders and set the route language. Legacy homepage ?lang=hu and ?lang=es URLs redirect to localized paths. Tests and documentation describe the localized routes and language switching.
Localized tool pages and navigation
src/pages/tools/localized/*, src/components/free-tools/ToolPageLayout.tsx, src/components/landing/*, src/i18n.ts, src/locales/*, src/locales/tools/*, docs/operations.md, .claude/skills/verify-debatelab/recipes/localization.spec.ts
Hungarian and Spanish copy supplies the tools index and four tool pages. Shared components render localized content and links. Pricing strings use configured trial and Pro price values, and landing-page links use localized paths.
Prerendering and localized SEO output
src/prerender/entry.tsx, src/lib/seo/*, scripts/prerender.mjs, scripts/sitemap.mjs, scripts/generate_seo_content.py, scripts/check-site.mjs, public/llms.txt, src/test/seo/page-head.test.tsx, docs/operations.md
Prerendered pages include language and alternate-link metadata. Canonical URLs and structured data use localized routes. Sitemap and llms.txt output includes localized entries, and check-site validates hreflang targets and alternate sets.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant App
  participant localizedRouteLoaders
  participant LocalizedToolPage
  participant LocalizedRoute
  participant ToolPageLayout
  Browser->>App: Request a localized tool URL
  App->>localizedRouteLoaders: Load the route page module
  localizedRouteLoaders->>LocalizedToolPage: Resolve the configured tool route
  App->>LocalizedRoute: Render with route and language
  LocalizedRoute->>LocalizedToolPage: Provide route language
  LocalizedToolPage->>ToolPageLayout: Render localized copy and tool links
Loading
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing.

Comment @coderabbitai help to get the list of available commands.

Programcode and others added 2 commits October 26, 2026 17:47
/?lang=hu and /?lang=es render the English page with canonical /, so
Google can list them as alternates of /. A 301 for the homepage only
sends them to /hu and /es. Vercel appends the original query to a
redirect destination, so the browser lands on /hu?lang=hu. That URL is
canonical to /hu, and the client drops ?lang= on load. ?lang= on every
other path, such as /app?lang=hu, keeps the client's own handling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The slugs now match what the pages call each tool: vitaidomero,
vitatetel-generator and jegyzetlap, in place of vita-idozito,
vitatema-generator and flow-lap. None was ever live, so no redirects.
Links, sitemap, hreflang and llms.txt follow from localized-routes.json.

The 19 copy fixes from the #236 review correct grammar, calques and
ambiguous words. The /hu title now says "vitafelkészülés". The tools
hub and motion generator heads say "vitatétel" like the page bodies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts resolved:
- i18n.ts: keep both default variable sets, product/brand/host from
  main and trialDays/proMonthly from this branch.
- en/es/hu.json: main's new hero headline and subheadline, this
  branch's {{trialDays}} trial badge.
- prerender/entry.tsx, check-site.mjs, docs/operations.md: keep both
  sides (main's entity graph and FAQ checks, this branch's localized
  pages and hreflang check).

GuideHub on /hu and /es now shows only the free tools at their
localized addresses, plus one link to the English guides on the home
page ("Angol nyelvű útmutatók", "Guías en inglés"). The Hungarian tool
labels use this branch's names (Vitaidőmérő, Vitatétel-generátor,
Jegyzetlap), and "cáfolást" follows the review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Programcode
Programcode marked this pull request as ready for review October 26, 2026 19:25
Copilot AI balanced review requested due to automatic review settings October 26, 2026 19:25
@Programcode
Programcode merged commit 12eaab1 into main Oct 26, 2026
3 of 10 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants