Internationalization (i18n)

Last Updated

July 16, 2026

This document records the current localization structure for The Pool and the supported workflow for adding languages in a fork.

The immediate shipped secondary locale is Spanish, but the real goal is to make future localization straightforward without custom code for the shared site-owned surfaces.

What Exists Now

The current i18n model covers:

  • structured locale config in _config.yml
  • shared translation catalogs in _data/i18n/
  • locale-aware URL helpers and a shared footer language switcher
  • localized public routes for:
    • /
    • /about/
    • /terms/
    • /campaigns/:slug/
    • /embed/campaign/
    • /pledge-success/
    • /pledge-cancelled/
    • /manage/
    • /community/
    • /admin/
    • /creator-campaign-checklist/
    • supporter community pages
  • localized site-owned runtime copy for cart, checkout, Manage Pledge, admin auth/dashboard/report previews/supporter browsing/content preview, new campaign creation, protected campaign previews, community flows, campaign countdowns (including screen-reader remaining-time status), upcoming-campaign launch reminders, hero-video/loading states and embed titles, supporter-community teaser chrome, diary tabs, production-phase controls, gallery labels, live-stats status text, and the campaign embed builder/widget
  • admin language switching preserves safe view state such as campaign filters and hashes, but strips admin_login magic-link tokens before linking to the alternate language
  • localized campaign-add-on section labels in both cart and Manage Pledge, plus checkout helper copy such as cart-button summaries, tax-location labels, and hosted-checkout next-step copy
  • localized campaign footer switching and localized campaign date formatting for public campaign chrome
  • localized campaign share-link labels plus state-aware share intent text for upcoming, live, funded, and ended campaigns
  • localized Worker supporter/admin emails and localized /manage/, /community/:slug/, /admin/, and protected preview links based on the relevant persisted or requested language
  • localized Worker campaign share-card routes such as /share/campaign/:slug.png?lang=es
  • localized public metadata and structured-data language hints on public pages and localized campaign pages

English remains the default locale. Spanish is the seeded secondary locale.

Config Model

The canonical locale config lives in _config.yml:

i18n:
  default_lang: en
  supported_langs:
    - en
    - es
  language_labels:
    en: English
    es: Español
  pages:
    home:
      en: /
      es: /es/
    about:
      en: /about/
      es: /es/about/
    terms:
      en: /terms/
      es: /es/terms/
    manage:
      en: /manage/
      es: /es/manage/
    admin:
      en: /admin/
      es: /es/admin/
    community_index:
      en: /community/
      es: /es/community/
    creator_campaign_checklist:
      en: /creator-campaign-checklist/
      es: /es/creator-campaign-checklist/
    pledge_success:
      en: /pledge-success/
      es: /es/pledge-success/
    pledge_cancelled:
      en: /pledge-cancelled/
      es: /es/pledge-cancelled/

This model intentionally keeps localization predictable for forks:

  • one default language
  • one supported-language list
  • one display-label map
  • one curated route map for shared locale-aware pages

Campaign pages are the main exception: they are generated from the campaign collection, so their localized routes are derived from campaign slugs and generated localized_paths rather than hand-written i18n.pages entries.

Translation Sources

1. Shared UI and runtime copy

Shared site-owned strings live in one YAML file per locale:

This includes:

  • nav labels
  • buttons
  • status labels
  • progress/meta text
  • cart / checkout / Manage Pledge runtime copy
  • admin dashboard tabs, filters, generated form labels, option labels, help text, media-upload copy, report/supporter/analytics/marketing copy, new campaign dialog copy, protected preview dialog copy, and content-editor controls
  • campaign add-on section labels and hosted/custom-checkout helper copy
  • community runtime copy
  • campaign countdown / hero-video / supporter-community / diary / production-phase / gallery / live-stats copy
  • upcoming-campaign launch reminder form, status, and email copy
  • campaign share labels and share-intent messages for Bluesky, X, Threads, Facebook, SMS, and email
  • campaign embed builder/widget copy
  • Worker supporter email copy

English is the canonical source file and fallback locale.

2. Long-form authored pages

Long-form page copy should use localized source files rather than trying to force every paragraph into YAML.

Examples:

That same pattern should be used for future content-heavy pages.

Routing Model

The site uses a static locale-prefix model:

  • default language stays on canonical URLs
    • /
    • /about/
    • /terms/
    • /manage/
    • /community/
    • /creator-campaign-checklist/
  • non-default languages live under a locale prefix
    • /es/
    • /es/about/
    • /es/terms/
    • /es/campaigns/{slug}/
    • /es/campaigns/{slug}/rewards/{tier-id}/ when a Shopping product is explicitly enabled
    • /es/embed/campaign/
    • /es/manage/
    • /es/community/
    • /es/creator-campaign-checklist/

This keeps the Jekyll/GitHub Pages deployment model simple and predictable.

Campaign collection routes are now generated in both locales, so the footer language switcher can remain available on campaign pages instead of disappearing or linking back to the default-language route.

Focused Shopping product pages follow the same generated locale-pair model. Product facts such as the campaign-authored reward name and description remain authored content; shared preorder, shipping, and final-sale disclosures come from the locale catalog.

Helpers and Runtime Plumbing

Shared locale helpers:

Runtime locale payloads:

Admin dashboard localization:

  • static admin shell copy in /_layouts/admin.html uses the shared Liquid translation helper
  • runtime admin copy is included in the full admin catalog emitted by runtime-messages-json.html
  • generated Settings and Campaigns fields come from Worker JSON, but assets/js/admin-dashboard.js localizes them with deterministic keys:
    • settings_section_* for top-level settings sidebar sections
    • settings_field_*_label, settings_field_*_help, and settings_field_*_placeholder for editable platform settings
    • settings_readonly_*_label and settings_readonly_*_help for platform read-only diagnostics and secret status rows
    • campaign_field_*_label and campaign_field_*_help for campaign settings and campaign-owned collections
    • campaign_readonly_*_label and campaign_readonly_*_help for campaign read-only rows
    • settings_option_*, campaign_option_*, and generic option_* keys for select/checkbox options
  • content editor controls, staged media-upload status/errors, media settings labels/help text, gallery settings, and gallery hover-caption controls are also runtime-localized; update English and Spanish together when adding a new admin media control
  • read-only runtime widgets such as Settings -> Plan usage should return stable payload IDs or optional labelKey values from the Worker, while browser display strings live in _data/i18n/{lang}.yml
  • the Worker should keep returning stable field path values; the client derives i18n keys from those paths so forks can add fields without duplicating render code
  • creator-authored campaign data, diary bodies, add-on names, decision options, and other saved content are displayed as authored; the shared catalog only localizes the surrounding dashboard UI

Important current behavior:

  • the footer language switcher is the shared locale switch surface
  • it preserves the current query string and hash
  • tokenized URLs such as /manage/?t=... can switch to /es/manage/?t=... without dropping pledge access
  • Stripe is initialized with the current locale where supported, so Stripe-owned field labels and validation can localize too
  • cart trigger summaries and tax-location helper copy come from the shared locale catalog, so custom checkout remains translatable without separate hardcoded strings
  • public campaign templates now route shared chrome strings through locale data instead of hardcoded English where practical, including the hero video CTA/loading state, hero-video embed titles, supporter-community teaser copy, launch reminder form/status copy, diary chrome, production-phase labels, gallery accessibility labels, campaign sidebar pledge copy, countdown screen-reader status text, and localized campaign dates
  • campaign pages use localized share labels and state-aware share-intent text while leaving creator-authored campaign titles and blurbs as authored
  • campaign pages now expose localized footer language switching through generated campaign localized_paths
  • the hosted campaign embed builder and widget pull their builder/runtime strings from the shared locale catalog and preserve locale-aware campaign return links
  • public metadata and JSON-LD now also follow the active page language, localized home route, and supported-language set so localized pages do not emit English-only crawl hints by accident
  • localized long-form pages such as About and Terms still use source-file translations, so doc/content sweeps need to keep those locale-specific files in sync manually

Worker Email Behavior

Worker supporter emails reuse the same locale catalog and persisted preferredLang.

Relevant files:

Practical behavior:

  • if no locale preference is captured, emails fall back to English
  • if a supporter pledges or manages from /es/..., the Worker can persist preferredLang=es
  • supporter emails and magic-link URLs then use the Spanish route model, such as /es/manage/?t=...
  • launch reminder emails use the signup’s persisted preferredLang and link back to the localized campaign route when one is available
  • campaign-assignment emails and protected preview reviewer emails use the shared email theme and locale catalog; reviewer preview email copy must state that the link expires in 24 hours
  • campaign share cards can also be requested in a locale-aware way, such as /share/campaign/sunder.png?lang=es

What a Locale YAML File Does and Does Not Do

Adding a new locale YAML file is enough for:

  • shared site chrome
  • shared runtime/browser messages
  • Worker supporter-email copy

It is not enough for a fully translated site by itself.

Full language support also needs:

  • the language added to i18n.supported_langs
  • its label added to i18n.language_labels
  • localized routes added to i18n.pages
  • localized source pages for any long-form content you actually want translated
  1. Copy /_data/i18n/en.yml to /_data/i18n/{lang}.yml.
  2. Add the language to the i18n block in /_config.yml.
  3. Add localized public-page routes to i18n.pages.
  4. Add localized source pages for long-form content such as /about/, /terms/, /manage/, or curated community index pages where needed.
  5. Verify generated collection routes such as /es/campaigns/{slug}/ and any locale-aware embed routes your deployment exposes.
  6. For admin dashboard changes, add matching admin keys in every locale file before shipping. In particular, generated fields need the deterministic settings_field_*, settings_readonly_*, campaign_field_*, or campaign_readonly_* keys described above; hand-built flows such as Create new campaign and Preview need explicit dialog/action/help keys.
  7. Run npm run test:i18n before shipping to verify supported locale catalogs keep the same nested key coverage as English.
  8. Run the local stack and verify both the shared UI copy and localized routes, including /admin/ and /es/admin/ when admin UI strings changed:
npm run test:i18n
npm run podman:doctor
./scripts/dev.sh --podman

Neutral US / Latin American Spanish Guide

The maintained Spanish locale targets clear, neutral Spanish for audiences in the United States and Latin America. Prefer direct, widely understood terms over regional slang, literal English syntax, or unexplained product jargon.

Use this glossary consistently in public pages and shared interface copy:

English concept Preferred Spanish Avoid in public copy
pledge aporte pledge
supporter / backer patrocinador / persona que apoya backer
reward tier nivel de recompensa tier
add-on complemento add-on
checkout proceso de pago checkout
fulfillment preparación y entrega fulfillment
shipping envío shipping
return devolución return
refund reembolso refund
preview vista previa preview
dashboard panel dashboard
analytics análisis analytics

Technical developer documentation may retain exact identifiers such as runtime, route names, setting paths, or provider product names when translation would make the implementation ambiguous. User-facing policy and About copy should not depend on those English identifiers.

For material policy changes, keep English and Spanish headings, anchors, obligations, time periods, remedies, and contact paths in direct parity. Automated key completeness does not replace a fluent human review; record the reviewer and date in release evidence when that review occurs.

Current Boundaries

Automated locale completeness and rendered i18n/SEO checks remain release gates. The v1.1.2 About, Terms, Shopping, shipping, and no-returns copy targets neutral US/Latin American Spanish, and the owner confirmed completion of the final fluent review on 2026-07-14. Require an explicit translator/native-speaker review plan before adding locales beyond English and Spanish.

Still intentionally out of scope for this model:

  • automatic translation of creator-authored campaign bodies, diary entries, or community post content
  • automatic translation of saved admin-authored campaign settings, add-on names, decision options, referral labels, or other content stored as campaign/platform data
  • locale-specific tax, shipping, or pricing rules
  • an in-repo machine-translation pipeline