Overhaul AuthDialog's login step:
- Add translation keys for every previously-hardcoded string (titles,
buttons, placeholders, status labels, validation/errors) across all
sixteen locales.
- The secret-key form is no longer collapsible — it's always open and is
the first option, followed by 'Log in with extension', then a
text-with-arrow link to the remote-signer step.
- Move the key-file upload icon onto the same row as the nsec input
(instead of beside the submit button); submit button is now full-width
below.
- Surface extension-login errors as a destructive toast rather than
writing them into the nsec input's inline error.
- Replace the centered 'Back' text buttons with a back arrow in the
top-left of the dialog header.
Also correct AGENTS.md's i18n section: the project ships fifteen
non-English locales (hi, id, sw, tr, zh-Hant were missing from the list).
Logged-in users change language via Settings, so the top-nav globe now
appears only when logged out (matching the search icon). On the homepage
hero, replace the 'Explore campaigns' button with 'Verify campaigns' and
order it before 'How it works'. Drop the now-unused exploreCampaigns
locale key across all locales.
Add /sponsors, a landing-style page for companies that want to partner
with the platform. Lays out the three ways to get involved: an
unrestricted gift to the Agora Seed Fund (BTC or USD), a matching pool
for an individual campaign, a featured campaign, or a curated list of
campaigns (e.g. political prisoners or women's sovereignty in Africa),
and promoting Agora donations to a customer base as a philanthropic
initiative.
Modeled on AboutPage's section recipe (dark hero, alternating section
backgrounds, hand-rolled cards) and routed under the wide
FundraiserLayout above the /:nip19 catch-all. Adds a Sponsors link to
the site footer. English copy only for now; other locales fall back to
English until the copy settles.
ProfileCard's namePlaceholder default was a hardcoded 'Your name'
literal used by the profile/org editor onboarding steps. Default it to
the new profile.namePlaceholder key (translated across all locales) and
resolve it via t() in the component body; callers that pass their own
placeholder (e.g. the campaign name step) are unaffected.
Keys added to en.json over time (notably the verifier create-account
onboarding flow, the walletDoubleTweak recovery namespace, and the
campaign-wizard tag step) were never propagated to the other locales,
so they silently fell back to English at runtime — which is why the
verify-campaign onboarding steps still rendered in English under e.g.
Chinese despite the components calling t().
Translate every key present in en.json but missing from each locale
(84-157 keys each, 1553 total) so all sixteen locales are now complete
with zero English fallbacks. Placeholders, technical tokens, and the
Agora name are preserved verbatim; plural categories follow each
language's grammar.
The nsec reveal input in the onboarding 'Save your key' step carried a
hardcoded English aria-label. Route it through
onboarding.secure.secretKeyAriaLabel and translate across all locales.
This was the last untranslated user-facing string in the verifier
create-account flow.
Drop the 'Welcome to Agora' header and the Nostr-flavored subtext from
the welcome step. The Agora bolt mark and wordmark are now the focal
point — laid out inline with the same tight spacing and Bebas display
recipe as the top-nav wordmark, just larger — and the wordmark doubles
as the dialog's accessible title.
Retitle the two buttons to 'Create new account' / 'Log in with
existing' (no Nostr phrasing) and route them through new auth.* keys,
translated across all sixteen locales.
The crop modal used in the verifier 'set up your organization'
onboarding step had hardcoded English controls (Reset, the
reposition/zoom hint, Cancel, Processing, Apply Crop, and the default
title). Route them through a new top-level imageCrop namespace and
add translations for all sixteen locales.
The VerifyTutorial demo card's 'of … goal' label was also hardcoded;
reuse the existing campaignsDetail.ofGoal key.
Swap the top-nav language trigger from the Languages glyph to Globe — the
more universal "change language" affordance.
Stop flipping individual RTL rows: the previous per-item dir="rtl" pushed
Arabic / Persian / Pashto names to the far edge while LTR rows hugged the
left, leaving a ragged, jarring column. Force every row LTR-aligned
(text-left) so the selected dot and the names share one clean left edge,
while each name keeps its own lang attribute so the browser still shapes
Arabic/Persian/Khmer/CJK glyphs correctly.
A visitor who lands in a language they can't read had no discoverable way
to change it: on desktop the only chrome was the search icon + login, and
the picker was buried at /settings/language (itself only reachable through
the mobile hamburger's "Settings" — a word they may not be able to read).
Add a globe/languages icon to the top-nav right cluster, visible to
everyone regardless of login state. It opens a dropdown listing every
supported language by its own native name (each row rendered in its own
lang/dir), so the right entry is recognizable no matter the current UI
language. Selecting one switches in place via changeAppLanguage without
navigating away, so scroll position and in-progress form state survive.
The /settings/language page remains the canonical deep-link.
New key nav.language added to en.json and all 15 other locales (reusing
each locale's existing language.title translation).
Wire the branch-new hardcoded UI strings through i18n:
- ProfileCard's shared image-edit menu ("Upload file", "Paste URL",
"Remove") and the website-slot placeholder now use t(); the component
gains useTranslation.
- The campaign profile step's name placeholder regressed from a
translated string to a literal "Campaign title" when it switched to the
shared identity card; restore it via a new
onboarding.profile.campaignNamePlaceholder key.
New keys added to en.json and translated into all 15 other locales:
profile.imageMenu.{upload,pasteUrl,remove}, profile.websitePlaceholder,
and onboarding.profile.campaignNamePlaceholder.
The plural keys flagged by a first pass (campaignsCount, seeAllCampaigns,
followersTitle, profile.verified.count, etc.) already resolve via
i18next's _one/_other suffixes and needed no change. Pre-existing
untranslated strings that predate this branch (e.g. ImageCropDialog) were
left untouched.
Regression-of: a7a9ed06
These onboarding/campaign surfaces accumulated several copies of the same
logic over the iterative work. Fold them into shared modules:
- Kind-0 publishing: the campaign-creator profile step reimplemented the same
read-modify-write publish that usePublishOrgProfile already had (fetch fresh
-> parse -> merge name/picture/banner/about -> publish with prev), plus its
own parseProfileMetadata. Route it through the shared hook.
- Draft type: the identical ProfileIdentityDraft and OrgProfileDraft
interfaces become one ProfileDraft in src/lib/profileDraft.ts, alongside
parseProfileMetadata/mergeProfileDraft helpers. The hook no longer imports
its draft type from a component module.
- Auto-grow textarea: the campaign story and verifier bio fields shared an
identical borderless textarea with copy-pasted height-resize handlers and
styling. Extract an AutoGrowTextarea component plus an autoGrowTextarea
helper (also reused by ProfileCard's editable fields).
The action now sits in a compact row beside the avatar, so the verbose
'Edit profile' label is unnecessary. Shorten profile.header.editProfile
to just 'Edit' across all locales.
On sm/md the Edit Profile / QR / more (or Follow / Donate) buttons sat in
a full-width row below the bio. Put them on the same row as the avatar,
bottom-right, Twitter/X-style: ProfileIdentityHeader gains a hideActionBar
prop, ActionBar is exported with an align prop, and the mobile layout
renders the avatar and a right-justified ActionBar (align="end") together.
With align="end" the Edit Profile button sizes to its label instead of
stretching full-width.