Language is not a barrier. 12 languages. Every one a first-class citizen. Static site translation, dynamic email translation, real-time map caption translation — all integrated.
Language is incidental. A user in Lahore submits a support ticket in Urdu. The admin reads it in English. The admin replies in English. The user receives the reply in Urdu. Neither party thinks about translation — the system handles it transparently. This is the design principle that drives every communication layer in The Trinity Beast.
The Trinity Beast serves 12 languages across every touchpoint:
The only content that remains English-only is the technical documentation itself — because the audience for those documents is developers who read English. Everything customer-facing is translated.
All 12 languages are supported by AWS Translate, ensuring both static and dynamic translation coverage.
| Language | Static i18n Keys | Email Templates | AWS Translate | RTL Support |
|---|---|---|---|---|
| English (en) | 1,000+ | All templates | Source language | No |
| Spanish (es) | 1,000+ | All templates | ✓ | No |
| Portuguese (pt) | 1,000+ | All templates | ✓ | No |
| French (fr) | 1,000+ | All templates | ✓ | No |
| German (de) | 1,000+ | All templates | ✓ | No |
| Russian (ru) | 1,000+ | All templates | ✓ | No |
| Hindi (hi) | 1,000+ | All templates | ✓ | No |
| Urdu (ur) | 1,000+ | All templates | ✓ | Yes |
| Italian (it) | 1,000+ | All templates | ✓ | No |
| Arabic (ar) | 1,000+ | All templates | ✓ | Yes |
| Japanese (ja) | 1,000+ | All templates | ✓ | No |
| Chinese (zh) | 1,000+ | All templates | ✓ | No |
The website uses a custom i18n engine (v5) that loads language-specific JSON files and applies translations to DOM elements via data-i18n attributes. Translation data is served from Valkey via the API first, with S3 as fallback — enabling instant updates without a deploy.
| Component | Location | Purpose |
|---|---|---|
| i18n Engine | js/i18n.js | Loads JSON, applies translations, manages language switching, browser detection, cpmp_site localStorage object |
| Language Files (S3) | lang/{code}.json | 12 files, ~1,000+ keys each — fallback source when API unavailable |
| Language API (Valkey) | GET /public/lang/{code} | Primary source — serves JSON from Valkey lang:{code} key. 5-min cache header. Updated instantly via POST /admin/lang/set. |
| RTL Stylesheet | css/rtl.css | Right-to-left layout for Arabic, Urdu |
| Flag Icons | icons/flag-{code}.svg | 12 SVG flags for the language dropdown |
| Language Dropdown | includes/header.html | Site-wide language switcher in the header |
cpmp_site.lang from localStorage (JSON object). Falls back to legacy cpmp-lang flat key (auto-migrated on first visit), then browser language detection.GET /public/lang/{code} from the API (Valkey-backed) first. Falls back to lang/{code}.json from S3 if the API returns empty or fails.data-i18n="section.key" have their text content replaced. data-i18n-html for HTML content. data-i18n-placeholder for form placeholders.cpmp_site.lang in localStorage.ar, ur, or pa, the dir="rtl" attribute is set and css/rtl.css activates mirrored layouts.window.cpmpI18n._cache) — no re-fetch on page navigation within the same session.| Section | Approx. Keys | Pages Covered |
|---|---|---|
| header / footer | 30 | All pages (shared) |
| home | 80 | Homepage |
| give | 45 | Give page |
subscribeListener | 250+ | Subscription page |
docLibrary | 142 | Document Library |
| support | 27 | Support page |
| map | 25 | Impact Map |
| Impact pages (7) | 350+ | Freedom, Water, Medical, etc. |
| team / origin | 100+ | Team, Origin Story |
| optout (2) | 20 | Newsletter opt-out pages |
All outbound email from The Trinity Beast is sent via Amazon SES and rendered in the recipient's preferred language.
| Email Type | Trigger | Translation Method | Sender |
|---|---|---|---|
| Support Confirmation | Ticket submitted | Go template with getSupportEmailStrings(lang) | Support@CPMP-Site.org |
| Support Reply | Admin replies to ticket | Go template + AWS Translate for reply body | Support@CPMP-Site.org |
| CPMP Newsletter Welcome | Newsletter subscription | SES Template + email_translations table | CPMP Mission |
| LPO Newsletter Welcome | LPO newsletter subscription | SES Template + email_translations table | The Trinity Beast |
| Subscription Receipt | Stripe payment | Lambda receipt processor | CPMP Mission |
| Newsletter Broadcast | Admin sends newsletter | TBCC Newsletter Console (Quill editor) | CPMP Mission / The Trinity Beast |
Every user interaction stores the language preference:
support_tickets.preferred_lang — set from the form submission's cpmp_site.lang (read from localStorage)newsletter_subscribers.preferred_lang — set at subscription timeusers.preferred_lang — set from Stripe checkout locale (passed via ?lang= query param)The Go server contains a getSupportEmailStrings(lang) function with hardcoded translations for all 12 languages. This covers all email chrome: headings, greetings, body text, labels, button text, and footer. The user's actual message content is passed through as-is (or translated via AWS Translate for replies).
Newsletter welcome emails use SES templated sending with field values loaded from the email_translations Aurora table. Each template has 6-11 translatable fields per language (heading, subject, body, button labels, unsubscribe text).
| Template | Fields per Language | Languages | Total Rows |
|---|---|---|---|
| CPMPNewsletterWelcome | 11 | 12 | 132 |
| LPONewsletterWelcome | 6 | 12 | 72 |
AWS Translate provides real-time neural machine translation for dynamic content that cannot be pre-translated in static JSON files.
| Use Case | Direction | Trigger | Caching |
|---|---|---|---|
| Support ticket (inbound) | Customer lang → English | Ticket submission (lang ≠ en) | Stored in message_en column |
| Support reply (outbound) | English → Customer lang | Admin reply (lang ≠ en) | Stored in message_translated column |
| Map pin captions | English → User's lang | Pin tap (lang ≠ en) | ElastiCache (30-day TTL) |
github.com/aws/aws-sdk-go-v2/service/translateecsTaskRole) with translate:TranslateText permissionDiagram 5.1 — AWS Translate Integration Flow
flowchart LR
subgraph Customer["Customer (any language)"]
C1[Submits ticket in Urdu]
C2[Receives reply in Urdu]
end
subgraph Server["ECS Container"]
S1[Support Handler]
S2[translateText fn]
S3[Reply Handler]
end
subgraph AWS["AWS Services"]
T[Amazon Translate API]
EC[ElastiCache
Translation Cache]
end
subgraph Admin["Admin (English)"]
A1[Reads ticket in English]
A2[Replies in English]
end
C1 -->|"POST /support/submit"| S1
S1 -->|"ur → en"| S2
S2 --> T
S2 -->|"message_en"| A1
A2 -->|"POST /support/reply"| S3
S3 -->|"en → ur"| S2
S2 -->|"message_translated"| C2
style C1 fill:#10b981,stroke:#059669,color:#fff
style C2 fill:#10b981,stroke:#059669,color:#fff
style A1 fill:#FF9900,stroke:#cc7a00,color:#fff
style A2 fill:#FF9900,stroke:#cc7a00,color:#fff
style S1 fill:#1e293b,stroke:#334155,color:#e2e8f0
style S2 fill:#635bff,stroke:#4b44cc,color:#fff
style S3 fill:#1e293b,stroke:#334155,color:#e2e8f0
style T fill:#60a5fa,stroke:#3b82f6,color:#fff
style EC fill:#a855f7,stroke:#7c3aed,color:#fff
cpmp_site.lang in localStorage)preferred_lang read from cpmp_site.langsupport_tickets.messagepreferred_lang != "en": AWS Translate converts to English, stored in support_tickets.message_ensupport_replies.messagepreferred_lang != "en": AWS Translate converts to customer's language, stored in support_replies.message_translated| Table | Column | Purpose |
|---|---|---|
support_tickets | message | Original message (customer's language) |
support_tickets | message_en | English translation (NULL if submitted in English) |
support_tickets | preferred_lang | Customer's language code (e.g., "ur", "pt") |
support_replies | message | Original reply (admin's English) |
support_replies | message_translated | Translated reply (customer's language, NULL if en) |
The Impact Map translates pin captions on-the-fly when a user taps a pin in a non-English language.
| Endpoint | Method | Body | Response |
|---|---|---|---|
/translate | POST | {"text":"...","target_lang":"ur"} | {"translated":"..."} |
The endpoint checks ElastiCache first (key: translate:{lang}:{text_hash}). On cache miss, calls AWS Translate and caches the result for 30 days. On cache hit, returns immediately with no API call.
Complete view of how language flows through every layer of The Trinity Beast communications system.
Diagram 9.1 — Full Multi-Lingual Communications Architecture
flowchart TD
subgraph User["User Browser"]
LS["localStorage
cpmp_site.lang (website)
cpmp_user.lang (dashboard)"]
DD[Flag Dropdown
Language Selector]
DD --> LS
end
subgraph Static["Static Translation Layer"]
JSON["lang/{code}.json
12 files × 1,000+ keys (S3 fallback)"]
VAPI["GET /public/lang/{code}
Valkey lang:{code} (primary)"]
I18N["i18n.js Engine v5"]
RTL["css/rtl.css
RTL Support"]
VAPI --> I18N
JSON -->|fallback| I18N
I18N --> Pages["All Pages
data-i18n attributes"]
LS --> I18N
end
subgraph Dynamic["Dynamic Translation Layer"]
API["/translate endpoint"]
EC["ElastiCache
translate:{lang}:{hash}
30-day TTL"]
AT["Amazon Translate
API"]
API --> EC
EC -->|miss| AT
AT --> EC
end
subgraph Email["Email Layer"]
SES["Amazon SES"]
ET["email_translations
Aurora Table
12 langs × 2 templates"]
GT["getSupportEmailStrings()
12 languages hardcoded"]
GT --> SES
ET --> SES
AT -->|reply translation| SES
end
subgraph Admin["TBCC Admin"]
NC["Newsletter Console
Quill Editor"]
SC["Support Console"]
NC --> SES
SC -->|English reply| AT
end
subgraph Map["Impact Map"]
Pins["Pin Captions
(English in Aurora)"]
Pins -->|tap| API
API -->|translated| Panel["Detail Panel"]
end
LS -->|preferred_lang| Email
style LS fill:#FF9900,stroke:#cc7a00,color:#fff
style DD fill:#FF9900,stroke:#cc7a00,color:#fff
style JSON fill:#10b981,stroke:#059669,color:#fff
style I18N fill:#10b981,stroke:#059669,color:#fff
style VAPI fill:#a855f7,stroke:#7c3aed,color:#fff
style AT fill:#60a5fa,stroke:#3b82f6,color:#fff
style EC fill:#a855f7,stroke:#7c3aed,color:#fff
style SES fill:#f59e0b,stroke:#d97706,color:#fff
style ET fill:#1e293b,stroke:#334155,color:#e2e8f0
style GT fill:#1e293b,stroke:#334155,color:#e2e8f0
style API fill:#635bff,stroke:#4b44cc,color:#fff
| Cache Layer | Key Pattern | TTL | Purpose |
|---|---|---|---|
| Valkey (ElastiCache) | lang:{code} | Permanent (no TTL) | Language JSON served by /public/lang/{code} API — updated instantly via POST /admin/lang/set without a deploy |
| ElastiCache (Valkey) | translate:{lang}:{text_hash} | 30 days | Map caption translations — avoid repeat API calls |
| Aurora (column) | support_tickets.message_en | Permanent | Inbound ticket translation — audit trail |
| Aurora (column) | support_replies.message_translated | Permanent | Outbound reply translation — audit trail |
Browser (localStorage) | cpmp_site | Permanent | Website preferences JSON object — lang (language code), theme (light/dark), lang_notice_dismissed (boolean). Written by i18n.js v5 and theme.js. |
Browser (localStorage) | cpmp_user | Session (cleared on logout) | Dashboard session JSON object — token (Bearer), email, name, roles, lang. Written on magic link auth. Lang flows from cpmp_site.lang at login time so dashboard respects the user's site language automatically. |
Browser (localStorage) | cpmp-reports-view | Permanent | Daily reports index preferences — JSON object with view (list or card) and sort (desc or asc) |
| Browser (memory) | cpmpI18n._cache | Session | Loaded JSON files — no re-fetch within session |
Legacy key migration: The old flat cpmp-lang localStorage key was replaced by cpmp_site.lang in i18n.js v5 (May 2026). On first page load after the upgrade, the engine automatically reads the old key, migrates the value into cpmp_site.lang, and removes the old key. Transparent to users — no action required.
Cost Efficiency: Each unique caption is translated only once per language. With ~100 map pins and 11 non-English languages, the maximum translation calls is ~1,100 — after which every subsequent tap is a sub-millisecond ElastiCache hit. Total one-time cost: approximately $0.02.
| Component | Monthly Cost | Notes |
|---|---|---|
| AWS Translate (support tickets) | < $0.50 | ~30 tickets/month × avg 200 chars × $15/million chars |
| AWS Translate (map captions) | < $0.05 | One-time cost, then cached. ~100 pins × 11 langs = 1,100 calls max |
| AWS Translate (future newsletter) | ~$1.00 | If broadcast translation enabled: ~2 newsletters/month × 2,000 chars × 11 langs |
| Static JSON files (S3 + CloudFront) | $0.00 | Included in existing S3/CloudFront costs |
| SES email sending | < $0.10 | $0.10 per 1,000 emails — well under 1,000/month |
| ElastiCache (translation cache) | $0.00 | Negligible additional memory on existing 52 GB node |
Total estimated monthly cost for full multi-lingual communications: < $2.00. Language accessibility for 12 languages, covering 600+ million native speakers, for less than the cost of a cup of coffee. This is what cloud-native architecture makes possible.