feat: base UI strings on carrier's shared Fluent source
Vendors carrier's locales/en-US/main.ftl (extended to cover gsmol's remaining strings) as the single source both clients draw wording from, adds a dependency-free .ftl-to-JSON build step (devbox run locale) since the file only ever uses Fluent's flat subset, and replaces every hardcoded string in index.html/app.js with a lookup into the generated dictionary. Along the way: removes connect()'s requirePin check, which turned out to be unreachable under the app's own invariants (every call site already guarantees its target host is pinned before calling it), and fixes a leftover call to store.setMasterSaved — removed in the earlier settings redesign — that was silently breaking onboarding's "I saved it" button.
This commit is contained in:
parent
80aca8a29f
commit
70087fa470
7 changed files with 1080 additions and 258 deletions
189
web/index.html
189
web/index.html
|
|
@ -30,7 +30,8 @@
|
|||
spellcheck="false" hidden>
|
||||
<div id="top-actions" class="top-actions" hidden>
|
||||
<button id="theme" class="icon-only" aria-label="theme"><svg class="icon"><use href="#i-auto"/></svg></button>
|
||||
<button id="settings-open" class="icon-only" aria-label="settings" title="settings"><svg class="icon"><use href="#i-settings"/></svg></button>
|
||||
<button id="settings-open" class="icon-only" data-l10n-aria-label="settings-nav-label"
|
||||
data-l10n-title="settings-nav-label"><svg class="icon"><use href="#i-settings"/></svg></button>
|
||||
<div id="account" class="account"></div>
|
||||
<div id="avatar" class="avatar" aria-hidden="true"></div>
|
||||
</div>
|
||||
|
|
@ -41,128 +42,124 @@
|
|||
screen's concern — e.g. the server screen never asks for a username. -->
|
||||
<main id="setup" class="setup" hidden>
|
||||
<section id="setup-intro" class="setup-card">
|
||||
<h2>welcome</h2>
|
||||
<p class="lead">Encrypted mail with no account and no password. Your identity is one secret,
|
||||
kept in this browser and nowhere else.</p>
|
||||
<button type="button" id="create-identity" class="filled large">create a new identity</button>
|
||||
<button type="button" id="show-restore" class="link">restore existing identity from seed</button>
|
||||
<h2 data-l10n-id="onboard-welcome-title">welcome</h2>
|
||||
<p class="lead" data-l10n-id="onboard-welcome-description">Encrypted mail with no account and no
|
||||
password. Your identity is one secret, kept in this browser and nowhere else.</p>
|
||||
<button type="button" id="create-identity" class="filled large" data-l10n-id="onboard-create-button">create a new identity</button>
|
||||
<button type="button" id="show-restore" class="link" data-l10n-id="onboard-restore-button">restore existing identity from seed</button>
|
||||
</section>
|
||||
|
||||
<section id="setup-seed" class="setup-card" hidden>
|
||||
<h2>write this seed down</h2>
|
||||
<p class="lead">It is the only secret, and it is shown once.</p>
|
||||
<h2 data-l10n-id="onboard-created-title">write this seed down</h2>
|
||||
<p class="lead" data-l10n-id="onboard-created-description">It is the only secret, and it is shown once.</p>
|
||||
<div class="seed-card">
|
||||
<div id="setup-seed-value" class="seed"></div>
|
||||
</div>
|
||||
<button type="button" id="seed-saved" class="filled large">I saved it</button>
|
||||
<button type="button" id="seed-back" class="link">back</button>
|
||||
<button type="button" id="seed-saved" class="filled large" data-l10n-id="onboard-saved-button">I saved it</button>
|
||||
<button type="button" id="seed-back" class="link" data-l10n-id="action-back">back</button>
|
||||
</section>
|
||||
|
||||
<section id="setup-restore" class="setup-card" hidden>
|
||||
<h2>restore from a seed</h2>
|
||||
<p class="lead">Paste the secret you saved. Your keys come back from it — it is all this client
|
||||
needs.</p>
|
||||
<h2 data-l10n-id="onboard-restore-title">restore from a seed</h2>
|
||||
<p class="lead" data-l10n-id="onboard-restore-description">Paste the secret you saved. Your keys
|
||||
come back from it — it is all this client needs.</p>
|
||||
<form id="restore-form" class="stack">
|
||||
<label>your secret
|
||||
<input id="restore-master" placeholder="hex or base32" spellcheck="false" required>
|
||||
<label><span data-l10n-id="onboard-seed-entry-title">your secret</span>
|
||||
<input id="restore-master" spellcheck="false" required>
|
||||
</label>
|
||||
<span id="restore-error" class="error"></span>
|
||||
<button class="filled large">restore</button>
|
||||
<button class="filled large" data-l10n-id="onboard-restore-go-button">restore</button>
|
||||
</form>
|
||||
<button type="button" id="restore-back" class="link">back</button>
|
||||
<button type="button" id="restore-back" class="link" data-l10n-id="action-back">back</button>
|
||||
</section>
|
||||
|
||||
<section id="setup-server" class="setup-card" hidden>
|
||||
<h2>set up the server</h2>
|
||||
<h2 data-l10n-id="signup-server-title">set up the server</h2>
|
||||
<form id="server-form" class="stack">
|
||||
<label>host
|
||||
<label><span data-l10n-id="signup-host-label">host</span>
|
||||
<input id="server-host" placeholder="example.org" spellcheck="false" required>
|
||||
</label>
|
||||
<div id="server-key-area" class="seed-card" hidden></div>
|
||||
<span id="server-error" class="error"></span>
|
||||
<button id="server-action" class="filled large">fetch the server's key</button>
|
||||
<button id="server-action" class="filled large" data-l10n-id="signup-fetch-key-button">fetch the server's key</button>
|
||||
</form>
|
||||
<button type="button" id="server-back" class="link">start over</button>
|
||||
<button type="button" id="server-back" class="link" data-l10n-id="signup-start-over-button">start over</button>
|
||||
</section>
|
||||
|
||||
<section id="setup-account" class="setup-card" hidden>
|
||||
<h2>choose your address</h2>
|
||||
<h2 data-l10n-id="signup-account-title">choose your address</h2>
|
||||
<div id="account-mode" class="mode-toggle" role="group">
|
||||
<button type="button" data-mode="new" class="active" aria-current="true">create a new address</button>
|
||||
<button type="button" data-mode="existing">I already have this address</button>
|
||||
<button type="button" data-mode="new" class="active" aria-current="true"
|
||||
data-l10n-id="signup-mode-new-label">create a new address</button>
|
||||
<button type="button" data-mode="existing" data-l10n-id="signup-mode-existing-label">I already have this address</button>
|
||||
</div>
|
||||
<form id="account-form" class="stack">
|
||||
<label>user
|
||||
<label><span data-l10n-id="signup-user-label">user</span>
|
||||
<input id="account-user" placeholder="alice" spellcheck="false" required>
|
||||
</label>
|
||||
<label id="account-token-label"><span>invite token <span class="hint">(optional)</span></span>
|
||||
<input id="account-token" placeholder="only if your server asks">
|
||||
<label id="account-token-label"><span data-l10n-id="signup-invite-label">invite token (if the server requires one)</span>
|
||||
<input id="account-token">
|
||||
</label>
|
||||
<span id="account-error" class="error"></span>
|
||||
<button id="account-action" class="filled large">register</button>
|
||||
<button id="account-action" class="filled large" data-l10n-id="signup-register-button">register</button>
|
||||
</form>
|
||||
<button type="button" id="account-back" class="link">back</button>
|
||||
<button type="button" id="account-back" class="link" data-l10n-id="action-back">back</button>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<main id="app" hidden>
|
||||
<nav class="rail" aria-label="folders">
|
||||
<button id="write" class="compose"><svg class="icon"><use href="#i-compose"/></svg>compose</button>
|
||||
<button id="write" class="compose"><svg class="icon"><use href="#i-compose"/></svg><span data-l10n-id="mail-write-button">compose</span></button>
|
||||
<div id="folders">
|
||||
<button data-folder="inbox" aria-label="inbox" class="active" aria-current="true">
|
||||
<svg class="icon"><use href="#i-inbox"/></svg><span class="label">inbox</span>
|
||||
<button data-folder="inbox" class="active" aria-current="true" data-l10n-aria-label="folder-inbox">
|
||||
<svg class="icon"><use href="#i-inbox"/></svg><span class="label" data-l10n-id="folder-inbox">inbox</span>
|
||||
<span id="count-inbox" class="count"></span>
|
||||
</button>
|
||||
<button data-folder="requests" aria-label="requests">
|
||||
<svg class="icon"><use href="#i-inbox"/></svg><span class="label">requests</span>
|
||||
<button data-folder="requests" data-l10n-aria-label="folder-requests">
|
||||
<svg class="icon"><use href="#i-inbox"/></svg><span class="label" data-l10n-id="folder-requests">requests</span>
|
||||
<span id="count-requests" class="count"></span>
|
||||
</button>
|
||||
<button data-folder="sent" aria-label="sent"><svg class="icon"><use href="#i-sent"/></svg><span class="label">sent</span></button>
|
||||
<button data-folder="contacts" aria-label="contacts"><svg class="icon"><use href="#i-contacts"/></svg><span class="label">contacts</span></button>
|
||||
<button data-folder="sent" data-l10n-aria-label="folder-sent"><svg class="icon"><use href="#i-sent"/></svg><span class="label" data-l10n-id="folder-sent">sent</span></button>
|
||||
<button data-folder="contacts" data-l10n-aria-label="contacts-caption"><svg class="icon"><use href="#i-contacts"/></svg><span class="label" data-l10n-id="contacts-caption">contacts</span></button>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="listpane">
|
||||
<div id="list-toolbar" class="toolbar">
|
||||
<div class="toolbar">
|
||||
<div id="mail-tools" class="tools">
|
||||
<button id="fetch" class="primary"><svg class="icon"><use href="#i-refresh"/></svg><span class="label">fetch</span></button>
|
||||
<button id="fetch" class="primary"><svg class="icon"><use href="#i-refresh"/></svg><span class="label" data-l10n-id="mail-fetch-button">fetch</span></button>
|
||||
<span id="list-status" class="list-status"></span>
|
||||
</div>
|
||||
<form id="import-form" class="tools" hidden>
|
||||
<input name="uri" placeholder="smol://alice@example.org/…" aria-label="smol:// address to import"
|
||||
spellcheck="false" required>
|
||||
<button class="primary">import</button>
|
||||
<input name="uri" placeholder="smol://alice@example.org/…" data-l10n-placeholder="contacts-import-placeholder"
|
||||
data-l10n-aria-label="contacts-import-placeholder" spellcheck="false" required>
|
||||
<button class="primary" data-l10n-id="contacts-import-button">import</button>
|
||||
</form>
|
||||
</div>
|
||||
<section id="list" class="list" aria-label="messages"></section>
|
||||
</div>
|
||||
<article id="view" class="view" aria-label="message"><p class="placeholder">select a message</p></article>
|
||||
<article id="view" class="view" aria-label="message"><p class="placeholder" data-l10n-id="mail-empty-title">select a message</p></article>
|
||||
</main>
|
||||
|
||||
<dialog id="compose">
|
||||
<form id="compose-form" method="dialog">
|
||||
<input name="to" required spellcheck="false" placeholder="to: alice@example.org or smol://…">
|
||||
<input name="subject" spellcheck="false" placeholder="subject">
|
||||
<textarea name="body" rows="12" placeholder="message"></textarea>
|
||||
<input name="subject" spellcheck="false" data-l10n-placeholder="compose-subject-label" placeholder="subject">
|
||||
<textarea name="body" rows="12" data-l10n-placeholder="compose-body-placeholder" placeholder="message"></textarea>
|
||||
<div id="compose-unpinned" class="seed-card" hidden>
|
||||
<h4>unpinned server</h4>
|
||||
<p class="hint">
|
||||
<strong id="compose-unpinned-host"></strong> is not pinned, and "require a pinned
|
||||
server" refuses to trust a key fetched from it for a new recipient. Fetch its key,
|
||||
compare the fingerprint against what the operator published through a trusted
|
||||
channel, then pin it to send.
|
||||
</p>
|
||||
<h4 id="compose-unpinned-title"></h4>
|
||||
<p id="compose-unpinned-body" class="hint"></p>
|
||||
<div id="compose-unpinned-key-area" hidden></div>
|
||||
<div class="actions">
|
||||
<button type="button" id="compose-unpinned-fetch">Fetch their server's key</button>
|
||||
<button type="button" id="compose-unpinned-pin" class="filled" hidden>Pin and send</button>
|
||||
<button type="button" id="compose-unpinned-fetch" data-l10n-id="compose-pin-button-fetch">Fetch their server's key</button>
|
||||
<button type="button" id="compose-unpinned-pin" class="filled" hidden data-l10n-id="compose-pin-button-send">Pin and send</button>
|
||||
</div>
|
||||
<span id="compose-unpinned-error" class="error"></span>
|
||||
</div>
|
||||
<footer>
|
||||
<label class="anon"><input type="checkbox" name="anonymous"> anonymous — omit my address</label>
|
||||
<label class="anon"><input type="checkbox" name="anonymous"> <span data-l10n-id="compose-anonymous-label">anonymous — omit my address</span></label>
|
||||
<span id="compose-error" class="error"></span>
|
||||
<button type="button" id="compose-cancel">cancel</button>
|
||||
<button id="send" class="filled" value="send">send</button>
|
||||
<button type="button" id="compose-cancel" data-l10n-id="action-cancel">cancel</button>
|
||||
<button id="send" class="filled" value="send" data-l10n-id="compose-send-button">send</button>
|
||||
</footer>
|
||||
</form>
|
||||
</dialog>
|
||||
|
|
@ -172,86 +169,88 @@
|
|||
all four at once was the overwhelming part. -->
|
||||
<dialog id="settings">
|
||||
<header class="dialog-head">
|
||||
<h2>settings</h2>
|
||||
<button type="button" id="settings-close" class="icon-only" aria-label="close settings"
|
||||
title="close"><svg class="icon"><use href="#i-close"/></svg></button>
|
||||
<h2 data-l10n-id="settings-nav-label">settings</h2>
|
||||
<button type="button" id="settings-close" class="icon-only" data-l10n-aria-label="settings-close-aria-label"
|
||||
data-l10n-title="settings-close-tooltip"><svg class="icon"><use href="#i-close"/></svg></button>
|
||||
</header>
|
||||
|
||||
<div class="settings-body">
|
||||
<nav id="settings-tabs" class="settings-tabs" aria-label="settings sections">
|
||||
<button type="button" data-tab="identity" class="active" aria-current="true">identity</button>
|
||||
<button type="button" data-tab="server">server</button>
|
||||
<button type="button" data-tab="backup">backup</button>
|
||||
<button type="button" data-tab="advanced">advanced</button>
|
||||
<button type="button" data-tab="identity" class="active" aria-current="true"
|
||||
data-l10n-id="settings-tab-identity">identity</button>
|
||||
<button type="button" data-tab="server" data-l10n-id="settings-tab-server">server</button>
|
||||
<button type="button" data-tab="backup" data-l10n-id="settings-tab-backup">backup</button>
|
||||
<button type="button" data-tab="advanced" data-l10n-id="settings-tab-advanced">advanced</button>
|
||||
</nav>
|
||||
|
||||
<div id="settings-panels" class="settings-panels">
|
||||
<section class="panel" data-panel="identity">
|
||||
<div id="identity-fields"></div>
|
||||
|
||||
<h3>recovery secret</h3>
|
||||
<h3 data-l10n-id="settings-recovery-secret-title">recovery secret</h3>
|
||||
<div id="identity-secret"></div>
|
||||
</section>
|
||||
|
||||
<section class="panel" data-panel="server" hidden>
|
||||
<h3>connection</h3>
|
||||
<h3 data-l10n-id="server-connection-title">connection</h3>
|
||||
<div id="server-connection"></div>
|
||||
|
||||
<h3>fetching</h3>
|
||||
<h3 data-l10n-id="server-fetching-title">fetching</h3>
|
||||
<label class="check-row">
|
||||
<input type="checkbox" id="leave-on-server">
|
||||
<span>
|
||||
<span class="check-title">Leave mail on server</span>
|
||||
<span class="hint">Keep a copy on the server after fetching, so another client can fetch
|
||||
it too. When this is enabled, deleting a message removes it from this client and the
|
||||
server.</span>
|
||||
<span class="check-title" data-l10n-id="server-leave-mail-title">Leave mail on server</span>
|
||||
<span class="hint" data-l10n-id="server-leave-mail-subtitle">Keep a copy on the server after
|
||||
fetching, so another client can fetch it too. When this is enabled, deleting a message
|
||||
removes it from this client and the server.</span>
|
||||
</span>
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<section class="panel" data-panel="backup" hidden>
|
||||
<p class="hint">One file holding your mail, contacts and server info for moving to another
|
||||
client, or for keeping what the server no longer has. It is sealed to this identity and
|
||||
deliberately leaves the secret itself out.</p>
|
||||
<p class="hint" data-l10n-id="backup-page-description">One file holding your mail, contacts and
|
||||
server info for moving to another client, or for keeping what the server no longer has. It
|
||||
is sealed to this identity and deliberately leaves the secret itself out.</p>
|
||||
<div class="action-row">
|
||||
<button type="button" id="export-data" class="filled">Export</button>
|
||||
<span class="hint">Write a backup file</span>
|
||||
<button type="button" id="export-data" class="filled" data-l10n-id="backup-export-title">Export</button>
|
||||
<span class="hint" data-l10n-id="backup-export-subtitle">Write a backup file</span>
|
||||
</div>
|
||||
<div class="action-row">
|
||||
<button type="button" id="import-data-trigger" class="outline">Import</button>
|
||||
<span class="hint">Restore from a backup file</span>
|
||||
<button type="button" id="import-data-trigger" class="outline" data-l10n-id="backup-import-title">Import</button>
|
||||
<span class="hint" data-l10n-id="backup-import-subtitle">Restore from a backup file</span>
|
||||
<input id="import-data" type="file" accept="application/json" hidden>
|
||||
</div>
|
||||
<span id="backup-error" class="error"></span>
|
||||
</section>
|
||||
|
||||
<section class="panel" data-panel="advanced" hidden>
|
||||
<h3>trust</h3>
|
||||
<h3 data-l10n-id="advanced-trust-section-title">trust</h3>
|
||||
<label class="check-row">
|
||||
<input type="checkbox" id="require-pinned-server">
|
||||
<span>
|
||||
<span class="check-title">Require a pinned server</span>
|
||||
<span class="hint">Refuse to look up a new recipient's key from a server you haven't
|
||||
pinned. An address that already carries a key, or a recipient you already know, is
|
||||
unaffected — this only applies to a new, hand-typed address.</span>
|
||||
<span class="check-title" data-l10n-id="advanced-strict-pinning-title">Require a pinned server</span>
|
||||
<span class="hint" data-l10n-id="advanced-strict-pinning-subtitle">Refuse to look up a new
|
||||
recipient's key from a server you haven't pinned. An address that already carries a key,
|
||||
or a recipient you already know, is unaffected — this only applies to a new, hand-typed
|
||||
address.</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<h3>rotations</h3>
|
||||
<p id="rotations-description" class="hint">Issues a fresh key from your secret with a signed
|
||||
certificate proving it replaces this one. Contacts accept the change from that chain, and
|
||||
the old key stays readable. Rotation is not revocation.</p>
|
||||
<h3 data-l10n-id="advanced-rotations-title">rotations</h3>
|
||||
<p id="rotations-description" class="hint" data-l10n-id="advanced-rotations-description">Issues
|
||||
a fresh key from your secret with a signed certificate proving it replaces this one. Contacts
|
||||
accept the change from that chain, and the old key stays readable. Rotation is not revocation.</p>
|
||||
<div id="rotations-count"></div>
|
||||
<button type="button" id="rotate-identity" class="outline">Rotate identity</button>
|
||||
<button type="button" id="rotate-identity" class="outline" data-l10n-id="advanced-rotate-button">Rotate identity</button>
|
||||
<span id="rotate-error" class="error"></span>
|
||||
|
||||
<h3 class="danger-head">log out</h3>
|
||||
<p id="logout-description" class="hint">Removes this identity from this client entirely: the
|
||||
secret, pins, contacts and every cached message. Without a backup of the secret there is no
|
||||
way back in. Mail already fetched here may have no other copy anywhere, unless "leave mail on
|
||||
server" was on. Whatever is still on the server comes back on the first fetch after logging
|
||||
back in.</p>
|
||||
<button type="button" id="logout" class="danger">Log out and delete all data</button>
|
||||
<h3 class="danger-head" data-l10n-id="advanced-logout-title">log out</h3>
|
||||
<p id="logout-description" class="hint" data-l10n-id="advanced-logout-description">Removes this
|
||||
identity from this client entirely: the secret, pins, contacts and every cached message.
|
||||
Without a backup of the secret there is no way back in. Mail already fetched here may have no
|
||||
other copy anywhere, unless "leave mail on server" was on. Whatever is still on the server
|
||||
comes back on the first fetch after logging back in.</p>
|
||||
<button type="button" id="logout" class="danger" data-l10n-id="advanced-logout-button">Log out and delete all data</button>
|
||||
<span id="logout-error" class="error"></span>
|
||||
</section>
|
||||
</div>
|
||||
|
|
@ -264,7 +263,7 @@
|
|||
<p id="confirm-text"></p>
|
||||
</div>
|
||||
<footer>
|
||||
<button type="button" id="confirm-cancel" autofocus>Cancel</button>
|
||||
<button type="button" id="confirm-cancel" autofocus data-l10n-id="action-cancel">Cancel</button>
|
||||
<button type="button" id="confirm-ok"></button>
|
||||
</footer>
|
||||
</dialog>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue