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_loginmagic-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:
- about.md
- es/about.md
- terms.md
- es/terms.md
- creator-campaign-checklist.md
- es/creator-campaign-checklist.md
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:
- /_includes/t.html
- /_includes/localized-url.html
- /_includes/language-switcher.html
- /_includes/localized-date.html
- /_includes/localized-datetime.html
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 sectionssettings_field_*_label,settings_field_*_help, andsettings_field_*_placeholderfor editable platform settingssettings_readonly_*_labelandsettings_readonly_*_helpfor platform read-only diagnostics and secret status rowscampaign_field_*_labelandcampaign_field_*_helpfor campaign settings and campaign-owned collectionscampaign_readonly_*_labelandcampaign_readonly_*_helpfor campaign read-only rowssettings_option_*,campaign_option_*, and genericoption_*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
labelKeyvalues from the Worker, while browser display strings live in_data/i18n/{lang}.yml - the Worker should keep returning stable field
pathvalues; 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 persistpreferredLang=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
preferredLangand 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
Recommended Fork Workflow
- Copy /_data/i18n/en.yml to
/_data/i18n/{lang}.yml. - Add the language to the
i18nblock in /_config.yml. - Add localized public-page routes to
i18n.pages. - Add localized source pages for long-form content such as
/about/,/terms/,/manage/, or curated community index pages where needed. - Verify generated collection routes such as
/es/campaigns/{slug}/and any locale-aware embed routes your deployment exposes. - For admin dashboard changes, add matching
adminkeys in every locale file before shipping. In particular, generated fields need the deterministicsettings_field_*,settings_readonly_*,campaign_field_*, orcampaign_readonly_*keys described above; hand-built flows such as Create new campaign and Preview need explicit dialog/action/help keys. - Run
npm run test:i18nbefore shipping to verify supported locale catalogs keep the same nested key coverage as English. - 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