Commit Graph

4090 Commits

Author SHA1 Message Date
Chad Curtis e00a135eb0 Merge branch 'main' of gitlab.com:soapbox-pub/agora into feat/my-square
# Conflicts:
#	src/components/TopNav.tsx
#	src/components/auth/AccountSwitcher.tsx
2026-05-24 16:27:43 -05:00
mkfain 17ac152d08 Add Traditional Chinese (zh-Hant) locale
Derived from the existing Simplified Chinese (zh) locale via OpenCC-style
s2twp conversion (Simplified -> Traditional with Taiwan phrase
preferences), then hand-corrected for the cases where automated
conversion picks the wrong vocabulary swap:

- 支援 only for technical 'X supports Y feature' contexts; 支持 retained
  for 'support a cause/person/activist' which is the more common sense
  for an activism platform.
- 連結 (link/connect) rather than 連線 (online) in the hero tagline
  'connecting activists to unstoppable funds'.
- 代碼 rather than 程式碼 for silent-payment / donation 'code'
  identifiers (the source is a payment-address token, not source code).
- 實例 rather than 例項 for 'instance' (self-hosted weserv).
- 影片 rather than 視頻 for 'video' (Taiwan vocab).
- Fixed an automated-conversion artifact 聚整合員 -> 聚集成員.

The Traditional resource is also registered under zh-TW and zh-HK so
that browsers reporting those device locales route directly to the
Traditional file instead of falling back to Simplified zh.

Mainland zh-CN continues to resolve to zh (Simplified) via i18next's
nonExplicitSupportedLngs. The language switcher dropdown shows both
'简体中文' and '繁體中文' as distinct choices.

Key parity verified: 1677 leaves, same as en.json and zh.json, zero
missing or extra keys, all {{placeholders}} and <N>markup</N> tokens
preserved verbatim from the source.
2026-05-24 16:11:03 -05:00
mkfain 97da8ae822 Add Han display font for Chinese hero text
Bebas Neue (the .font-display family) ships only Latin glyphs, so
Chinese hero headlines fall back to system fonts and lose their
industrial display character. Add @fontsource/noto-sans-tc weight 900
and a :lang() rule that swaps it in for any .font-display element
while the page language is a Chinese variant (zh, zh-Hant, zh-TW,
zh-HK).

The fontsource CSS uses unicode-range descriptors, so non-Chinese
users do not download the Han glyph slices (effectively zero cost
for Latin-only locales).

The rule reverses Tailwind's italic, uppercase, tracking, and the
hero's 0.022em -webkit-text-stroke fatten trick — none of those are
meaningful for CJK text and the stroke trick muddies strokes at
weight 900.
2026-05-24 15:57:52 -05:00
mkfain 88a5f45eac i18n: add Turkish (tr) locale
Adds a complete Türkçe translation. Turkish is spoken by ~90M people
and has an unusually strong mission fit for Agora: Turkey has
documented Bitcoin demand from currency-crisis users, journalist
crackdowns, and an active diaspora that often funds back home.

Register is modern İstanbul Turkish (BBC Türkçe / WhatsApp Türkçe
style) — polite-but-direct, formal siz throughout. English loanwords
kept where they're standard in everyday Turkish (Bitcoin, Lightning,
Nostr, röle, zap); Turkish equivalents used where they read naturally
(cüzdan, gönderi, anlık bildirim).

{{appName}} suffixing uses the standard Turkish apostrophe convention
for proper nouns ({{appName}}'da, {{appName}}'nın, etc.). Suffixes
are pinned to back-vowel harmony because "Agora" ends in 'a' — this
will break if AppConfig.appName is ever changed to a front-vowel word
like "Eylem" or "Yardım", but that's a deliberate trade-off for
not having to fork the JSON across deployments.

heroTagline mirrors the English design rhythm — line break before the
highlight, with the inline-block orange box on its own line as the
visual centerpiece:
"Aktivistleri<1></1><0>durdurulamaz</0> finansmana bağlıyoruz."
The trailing period is glued to a multi-word phrase rather than
sitting alone after the inline-block, avoiding the wrap bug we fixed
for Portuguese.

Turkish casing note: the hero h1 has text-transform: uppercase. Turkish
has dotted/dotless i (İ/i, I/ı). CSS uppercase honors the lang="tr"
attribute that applyDocumentDirection sets on <html>, so dotted
lowercase "i" uppercases to "İ" correctly without manual handling.

i18n auto-detection picks up the new locale on devices set to Turkish
(navigator.language returns tr/tr-TR).

Validation: en/tr leaf counts both 1677, no extra/missing keys,
locales.test.ts passes (120 tests, +2 for the new locale).
2026-05-24 15:30:42 -05:00
mkfain e25f575d18 i18n: add Indonesian (id) locale
Adds a complete Bahasa Indonesia translation. Indonesian is the
eleventh-most-spoken language worldwide (~200M speakers) and brings
Agora to Indonesia's growing Bitcoin-aware activist community plus the
broader maritime Southeast Asia.

Register is standard Bahasa Indonesia (formal-but-conversational,
the style used by Kompas, Tempo, Tirto). Avoids Malaysian-Malay-only
vocabulary so Malaysian users (handled via nonExplicitSupportedLngs
folding ms-* → id is NOT happening here; ms users will fall back to
English until we add ms separately, which is acceptable given the
~85% mutual intelligibility). Uses Anda for the second person, the
established UI convention. Standard Indonesian tech vocabulary where
present (dompet, kampanye, donasi, donatur, ikrar, penggalangan dana,
pengaturan); English loanwords kept where they're normal in modern
Indonesian software (Bitcoin, Lightning, Nostr, feed, post, relay,
zap, NIP, BIP).

heroTagline uses the period-inside-highlight pattern:
"Menghubungkan aktivis dengan<1></1>pendanaan <0>tak terbendung.</0>"
The trailing period lives inside the orange highlight span, so it
can't orphan to its own line at the hero's text-8xl size — the same
fix we applied for Portuguese.

i18n auto-detection picks up the new locale on devices set to
Indonesian (navigator.language returns id/id-ID).

Validation: en/id leaf counts both 1677, no extra/missing keys,
locales.test.ts passes (118 tests, +2 for the new locale).
2026-05-24 15:29:36 -05:00
mkfain 348b86d22b i18n: add Hindi (hi) locale
Adds a complete Hindi translation alongside the existing 11 locales.
Hindi (हिन्दी) is the fourth-most-spoken language in the world (~610M
speakers) and brings Agora to a large activist population currently
served only by the English fallback.

Translation register is Hindustani / news-style (BBC Hindi-flavored),
not Sanskrit-leaning शुद्ध हिन्दी — chosen for accessibility to the
broadest Hindi-speaking audience, including users who'd otherwise
struggle with formal government Hindi. Uses आप throughout for the
second person. Standard tech loanwords kept transliterated where they
read naturally (वॉलेट, पोस्ट, कैंपेन, फ़ीड); brand names and
protocol tokens (Bitcoin, Nostr, BIP-352, nsec, sp1…) kept in Latin
script.

heroTagline uses the safe structure — short highlight on the adjective
"अजेय" with a trailing phrase ending in danda (।), avoiding the
inline-block-followed-by-lone-punctuation wrap bug that previously hit
the Portuguese locale.

i18n auto-detection picks up the new locale on devices set to Hindi
(navigator.language returns hi/hi-IN), with nonExplicitSupportedLngs
folding regional variants into the hi bucket. The runtime fallback to
English still works for any keys we might miss in future edits.

Validation: en/hi leaf counts both 1677, no extra/missing keys,
locales.test.ts passes (116 tests, +2 for the new locale).
2026-05-24 15:28:31 -05:00
mkfain 2b544d1f7a fix(i18n): keep trailing period inside hero highlight in pt locale
The Portuguese heroTagline read

  "Conectando ativistas a<1></1><0>financiamento incontrolável</0>."

where the period sits outside the <0>...</0> orange highlighter span.
That span is rendered as inline-block w-fit (CampaignsPage.tsx:203-207),
and the boundary between an inline-block and the following bare text
node is a soft-wrap opportunity. When the highlighted phrase
"financiamento incontrolável" filled the line — which it does at the
hero's text-7xl/8xl sizes inside max-w-2xl — the trailing period
wrapped to its own line.

Restructure the markup so only the adjective is highlighted and the
period lives inside the orange box, mirroring the design intent of the
English original (where the highlight is on the adjective and the noun
trails it):

  "Conectando ativistas a<1></1>financiamento <0>incontrolável.</0>"

"financiamento " is now plain text before the box, and the period is
part of the box's content, so there is no wrap opportunity between the
last visible character and the period.

Other locales (fr, ru) have the same structural shape but short enough
highlighted words that the issue doesn't manifest in practice; leaving
them alone.
2026-05-24 12:55:48 -05:00
mkfain bf87c21587 fix(faq): correct address derivation source, lead silent-payments answer with wallet send support
The donation address derivation FAQ said "derived from your Nostr public
key," but the HD wallet (src/lib/hdwallet/derivation.ts) actually uses
the user's nsec (secret key) as the BIP-32 master seed for both the
BIP-86 Taproot wallet and the BIP-352 silent-payments wallet. Updated
the wording across why-not-rotating-addresses, what-is-nostr, the donor
guide's arrivesDirectly step, and the activist guide's howReceiving
intro.

The 'Does Agora support silent payments?' answer only mentioned the
activist receive side. Agora's own wallet supports sending silent
payments too (HDSendBitcoinDialog accepts sp1… addresses via the
BIP-352 sender in src/lib/hdwallet/sp/sender.ts), so the answer now
leads with that on the send side and follows with the existing
activist-receive paragraphs.

Propagated to all 10 non-English locales (ar, es, fa, fr, km, ps, pt,
ru, sn, zh).
2026-05-24 12:48:30 -05:00
mkfain 3dac23d2af i18n: translate new sections in ru/fr/pt locales
Catches the ru/fr/pt locales up with the keys added upstream while the
initial translations were in flight: translate, forms additions,
organizationContext, groups.detail, calendarEvents, follow,
campaignsDetail.openInWallet, the assorted countryPlaceholder /
showLess / readMore entries, and a handful of scattered new keys.
2026-05-24 11:50:25 -05:00
mkfain e7e0236fa7 i18n: translate Privacy + CSAE policy markdown into Russian, French, Brazilian Portuguese
Adds src/content/privacy/{ru,fr,pt}.md and src/content/csae/{ru,fr,pt}.md,
and registers them in usePolicyMarkdown.ts so the policy pages serve the
matching translation when the user's language is set to one of the three
new locales (falling back to en otherwise).
2026-05-24 11:43:49 -05:00
lemon d11cc212d7 fix: update new locale keys 2026-05-24 00:46:09 -07:00
lemon 157e04fed1 fix: localize shared creation form copy 2026-05-24 00:40:27 -07:00
lemon 8f0de73d86 fix: localize event creation page 2026-05-24 00:40:27 -07:00
lemon 98b4df47d1 fix: restore event creation page 2026-05-24 00:40:27 -07:00
lemon 64ac109e6c fix: translate event detail page 2026-05-24 00:40:27 -07:00
lemon 81a765aef1 fix: translate detail page labels 2026-05-24 00:40:27 -07:00
lemon 78d680fa37 fix: localize feed mode switcher 2026-05-24 00:37:04 -07:00
lemon 2df55a731a fix: translate compose destination strings 2026-05-24 00:34:04 -07:00
lemon 00eb2128c3 fix: place card translate controls in footers 2026-05-24 00:30:29 -07:00
lemon 4d40099426 feat: translate preview cards 2026-05-24 00:30:29 -07:00
lemon 9392cc5061 feat: translate event detail cards 2026-05-24 00:30:29 -07:00
lemon 24ba779da6 fix: map Persian and Pashto translation targets 2026-05-24 00:30:29 -07:00
lemon 0efd72268e feat: translate campaign cards 2026-05-24 00:30:29 -07:00
lemon 3e7b7e824f fix: move note translation action 2026-05-24 00:30:29 -07:00
lemon 8d7d81dd3f feat: add note translation button 2026-05-24 00:30:29 -07:00
mkfain ab48ab3aa3 i18n(ru): translate guides (donor + activist) — Russian locale complete 2026-05-24 00:46:15 -05:00
mkfain 60f745af0b i18n(ru): translate profileSettings, faq 2026-05-24 00:44:09 -05:00
mkfain 431f3e43de i18n(ru): translate notifications, notifSettings, search, profile 2026-05-24 00:41:14 -05:00
mkfain 47bccf4d7d i18n(ru): translate wallet, walletRecovery, walletConnect, walletSend, bitcoinPublic, spScan 2026-05-24 00:39:21 -05:00
mkfain fb84d29898 i18n(ru): translate campaignsDetail, campaigns, settings, language, organizers 2026-05-24 00:37:32 -05:00
mkfain 4833f26847 i18n(ru): translate policyPages, about, forms, notFound, pledges, groups, campaignsCreate 2026-05-24 00:35:54 -05:00
mkfain fefe2cae1e i18n(ru): translate common, nav, auth, feed, compose, noteCard, noteMoreMenu 2026-05-24 00:31:33 -05:00
mkfain 6100ad657c i18n: translate Brazilian Portuguese (pt) locale 2026-05-24 00:29:19 -05:00
mkfain 3f8fe5d1f6 i18n: translate French (fr) locale 2026-05-24 00:13:16 -05:00
mkfain 614966f764 i18n: scaffold Russian, French, and Brazilian Portuguese locales
Bootstraps src/locales/{ru,fr,pt}.json from en.json as a starting
point and wires all three into i18n.ts. Strings are still English
pending the per-section translation passes that follow.
2026-05-23 23:57:12 -05:00
mkfain 90a1b17c07 i18n: translate CSAE policy into km and sn
Fills the last two gaps in CSAE policy coverage. Khmer and Shona
markdown files land beside the existing six (en, es, zh, ar, fa, ps),
and both entries are registered in the usePolicyMarkdown loader.

Every long-form policy / guide surface is now translated in all eight
shipped locales.
2026-05-23 23:19:04 -05:00
mkfain 75d5b7a09d i18n: translate Donor / Activist guides into es, zh, ar, fa, ps, km, sn
Ships the full guides.* namespace in every non-English locale:

  * guides.shared (tldr eyebrow, payment badge labels, payment
    comparison table headers and all 4 donor rows / 7 activist rows)
  * guides.donor (tldr, flow steps, comparison footnote, two callouts,
    privacy optionGrid, silentToday prose)
  * guides.activist (tldr, two prose sections, comparison footnote,
    silentToday prose, movePromptly steps, cashout optionGrid, tumblers
    callout)

Product / wallet / project names (Bitcoin, Nostr, BIP-352, Ditto Wallet,
Dana, Sparrow, BlueWallet, Phoenix, Bisq, Wasabi, Boltz, Bitrefill,
HodlHodl, RoboSats, Cash App, Coinbase, Strike, Venmo, Kraken, Binance,
PayPal, Amazon, Uber) and tag chips (non-custodial, BIP-352, low fees,
etc.) stay in English by design — the chips live in code, not in
locales.
2026-05-23 23:07:49 -05:00
mkfain f66e6e80c1 i18n: route Donor / Activist guide strings through locales
Splits the guide content same way the FAQ was split:

  * Structure (block order, kinds, audience, callout variant, option
    grid chips/hrefs) stays in helpContent.ts as a typed array of
    GuideBlockStructure descriptors with stable IDs.
  * Every user-visible string moves to en.json under guides.donor.*,
    guides.activist.*, and guides.shared.* (badge labels, tldr eyebrow,
    payment comparison table headers + rows).

PaymentComparisonTable, InlinePaymentBadge, and GuideTLDR call
useTranslation() so a language switch triggers re-render. DonorGuidePage
and ActivistGuidePage already do, so getDonorGuideBlocks /
getActivistGuideBlocks re-run on every render and pick up fresh i18n
values automatically.

This commit lands the en strings only; non-en locales follow in the
next commit.
2026-05-23 23:07:49 -05:00
Alex Gleason eed83796f2 Replace footer copyright with source code link 2026-05-23 22:00:56 -05:00
mkfain b84ddd5c39 i18n: translate FAQ into es, zh, ar, fa, ps, km, sn
Ships the `faq.*` namespace in every non-English locale. Each locale
gets all 4 category labels and all 23 items (15 visible across About
Agora / Bitcoin Donations / About Nostr + 8 hidden legacy items used by
`HelpTip` call sites in settings pages).

Missing-key fallback to English still works via i18next's default
fallback chain — if a future FAQ item lands in en.json before the other
locales catch up, those locales will fall back to English at that key
without breaking the renderer.
2026-05-23 21:32:00 -05:00
Alex Gleason ca74e87ea3 Merge branch 'main' of gitlab.com:soapbox-pub/agora
# Conflicts:
#	src/lib/helpContent.ts
2026-05-23 21:11:19 -05:00
Alex Gleason 6f187d580f Merge branch 'main' of gitlab.com:soapbox-pub/agora
# Conflicts:
#	src/components/music/MusicTrackRow.tsx
#	src/pages/ActionDetailPage.tsx
2026-05-23 21:09:16 -05:00
mkfain e63a08c2e2 i18n: route FAQ strings through the locale namespace
Splits the FAQ definition in `helpContent.ts` into two layers:

  * a structural template (category order, item IDs, hidden flag) that
    stays in TS, and
  * a flat `faq.*` namespace in `locales/*.json` that holds every
    user-visible string (category labels, questions, answer paragraph
    arrays).

`getFAQCategories` / `getFAQItems` / `getFAQItem` keep the same
`(appName)` signature and `FAQCategory` / `FAQItem` shape — internally
they resolve strings through `i18n.t()`, with `{appName}` literals
rewritten to `{{appName}}` for i18next interpolation. Answer arrays use
`returnObjects: true` so a missing locale entry falls back to English
without breaking the renderer.

`HelpFAQSection` reads `i18n.language` to re-resolve on language switch;
`HelpTip` calls `useTranslation()` for the same reason. The Donor /
Activist guide blocks below are still keyed off the original
single-brace `{appName}` literal — that's a separate i18n pass.
2026-05-23 21:05:46 -05:00
Alex Gleason 29fd0c9a0f Remove unused exports and dead code
Aggressive cleanup of 359 exports across 153 files identified as
having zero importers outside their declaring module:

- 105 symbols deleted entirely (no internal uses either)
- 254 symbols un-exported (still referenced file-locally; dropped the
  `export` keyword to shrink the public surface)
- ~70 cascade cleanups of locals that became dead once their sole
  consumer was removed

Notable shrinkage:
- src/hooks/useShakespeare.ts: 626 \u2192 22 lines (unwired AI chat surface;
  only the ChatMessage type is consumed)
- src/hooks/useTrending.ts: only useEventStats survives; trending feed
  hooks were never wired up
- src/hooks/useTrustedCountryStats.ts: dead type re-exports removed
- src/lib/bitcoin.ts: PSBT helpers \u2014 unused wallet feature scaffolding
- src/lib/communityUtils.ts: unused NIP-72 moderation helpers
- src/lib/extraKinds.ts, src/lib/colorUtils.ts: unused helpers
- src/lib/logger.ts: bare debug/info/warn/error exports dropped;
  consumers use the `logger` object
- src/lib/aiChatSystemPrompt.ts: trimmed to the
  DEFAULT_SYSTEM_PROMPT_TEMPLATE constant
- src/components/music/MusicTrackRow.tsx: dead row component removed;
  only the skeleton is consumed

src/hooks/useNostr.ts (intentional decoy) and src/i18n.ts
(side-effect import) were preserved per their respective contracts.
2026-05-23 20:56:43 -05:00
mkfain 15b4549714 i18n: translate CSAEPolicyPage via markdown loader
Extracts the ~285-line JSX prose body into per-language markdown files
served through the existing `usePolicyMarkdown` hook + `PolicyMarkdown`
component shipped with the privacy page.

Ships en/es/zh/ar/fa/ps. Khmer and Shona are left unregistered for now
and fall back to English at runtime via the loader's en fallback path —
the policy text is dense and translators will want a careful pass.

No new infrastructure; reuses the loader + renderer end-to-end.
2026-05-23 20:55:15 -05:00
mkfain 06432d2155 i18n: translate PrivacyPolicyPage via markdown loader
Extracts the ~110-line JSX prose body into per-language markdown files
and renders it through a new `usePolicyMarkdown` hook + `PolicyMarkdown`
component. Eight locales ship beside `en.md`; missing locales fall back
to English at runtime.

The loader uses dynamic `import()` of `*.md?raw` (Vite + Bun friendly,
no `import.meta.glob`) keyed on a static `{ slug: { lng: loader } }`
table so bundlers can code-split per locale and we keep one shared chunk
for the markdown ecosystem. `{{appName}}` placeholders are interpolated
at render time with backslash-escape of markdown specials, and the
rendered output goes through rehype-sanitize.

This is the reusable loader infrastructure for the long-form policy
pages — CSAEPolicyPage is the next consumer.
2026-05-23 20:41:50 -05:00
Alex Gleason 5500ccc188 Delete orphan source files
Four files with zero importers across the repo:

- src/components/TeamSoapboxCard.tsx
- src/components/discovery/ProfileCard.tsx (superseded by
  src/components/ProfileCard.tsx)
- src/components/letter/StickerPicker.tsx (superseded by
  src/components/StickerPicker.tsx)
- src/hooks/useDominantColor.ts

src/hooks/useNostr.ts is also orphan but intentional \u2014 it exists as
a re-export decoy and is kept.
2026-05-23 20:29:26 -05:00
Alex Gleason 407cc72a6e Remove deprecated createGeoIdentifier function
Annotated for removal after migration period with zero in-tree callers.
Consumers should use createCountryIdentifier() instead.
2026-05-23 20:29:08 -05:00
Alex Gleason 64546f5a7f Remove unused npm dependencies
Drop 13 packages that have zero imports across src/, configs, and
native projects:

- @radix-ui/react-{menubar,navigation-menu,aspect-ratio,context-menu}
  (no shadcn primitive consumes them)
- react-leaflet, leaflet, @types/leaflet (no map usage)
- smol-toml, fflate, html-to-image, input-otp
- react-resizable-panels, react-day-picker

Also drops the orphaned .leaflet-* CSS overrides in src/index.css.
2026-05-23 20:28:52 -05:00
Alex Gleason 7483c7e302 Remove orphan challenge-covers assets
The 11 cover images in /public/challenge-covers/ were superseded by
Blossom-hosted URLs in DEFAULT_ACTION_COVERS. No code referenced the
local files; the comment claiming otherwise was stale.

Reclaims ~4.4 MB from the bundle.
2026-05-23 20:28:16 -05:00