mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-01 16:05:39 +00:00
Merge branch 'cu-platform' into catch-up-2026-11
# Conflicts: # locales/rn/en.json # src/stores/settings-store.ts
This commit is contained in:
@@ -708,8 +708,10 @@ export default function App() {
|
||||
// content, since hydration hands back an equal but new object; the value
|
||||
// seen first after hydration is the baseline, not a change.
|
||||
const notificationMutes = useSettingsStore((s) => s.notificationMutes);
|
||||
// The Inbox-only switch changes the same filter (webmail #983).
|
||||
const pushInboxOnly = useSettingsStore((s) => s.pushNotifyInboxOnly);
|
||||
const settingsHydrated = useSettingsStore((s) => s.hydrated);
|
||||
const mutesKey = JSON.stringify(notificationMutes);
|
||||
const mutesKey = JSON.stringify([notificationMutes, pushInboxOnly]);
|
||||
const lastMutesKey = React.useRef<string | null>(null);
|
||||
React.useEffect(() => {
|
||||
if (!settingsHydrated) return;
|
||||
|
||||
@@ -60,12 +60,12 @@ Findings:
|
||||
- What RN does: .maestro/subflows/stalwart-login-page.yaml types and taps Sign in, then inbox.yaml waits for the tab bar. The app doesn't depend on the order (oauth-native.ts never sends `prompt=consent`). The iOS e2e server (`vars.E2E_SERVER`) is the 0.16 server, so CI isn't affected today.
|
||||
- Fix hint: optional `runFlow when visible "Allow"` tap at the end of the subflow.
|
||||
|
||||
- [ ] **A `consentRequired` answer reads as a wrong password** — `P3` — `bug`
|
||||
- [x] **A `consentRequired` answer reads as a wrong password** — `P3` — `bug` — fixed in 1bef4719
|
||||
- What Stalwart does: new answer `{type:'consentRequired', token}` and request `{type:'authConsent', token, query, consent}` in 1.0.1.
|
||||
- What RN does: readStalwartLoginResponse maps it to invalid credentials (src/lib/totp-login.ts:344-380). Not reachable while `consent: true` is sent. The webmail has the same gap (lib/auth/stalwart-login-request.ts).
|
||||
- Fix hint: answer it with `authConsent` and the same query, or a clear error.
|
||||
|
||||
- [ ] **The encrypt-on-upload copy no longer matches the server** — `P3` — `docs`
|
||||
- [x] **The encrypt-on-upload copy no longer matches the server** — `P3` — `docs` — fixed in 853cb5d0 (comment; the live compose check is not written)
|
||||
- What Stalwart does: 1.0.1 stores JMAP `$draft` messages and the Sent copy unencrypted (crates/email/src/message/ingest.rs:608-611).
|
||||
- What RN does: no workaround for encrypted drafts; the comment at src/api/account-security.ts:249-254 says sent mail is encrypted too.
|
||||
- Fix hint: fix the comment; a live check in live-stalwart-compose that a draft and the Sent copy read back as text with encryption on.
|
||||
@@ -134,9 +134,9 @@ current lines is in "Webmail bugs" at the end of this file.
|
||||
|
||||
### UX audit deferred (docs/ux-audit-2026-10.md)
|
||||
|
||||
- [ ] **U-133 no way out of a hung "Signing in"** — `P2` — `missing` — src/screens/LoginScreen.tsx:218 swallows Back while busy, SigningInStep has no cancel; abortable sign-in, M
|
||||
- [x] **U-133 no way out of a hung "Signing in"** — `P2` — `missing` — src/screens/LoginScreen.tsx:218 swallows Back while busy, SigningInStep has no cancel; abortable sign-in, M — fixed in 55d008a2
|
||||
- [ ] **U-069 recipient suggestions hidden in phone landscape** — `P2` — `missing` — needs a full-screen picker, M (feature)
|
||||
- [ ] **U-045 German Prev/Next read "Zurück"/"Weiter"** — `P3` — `bug` — override in locales/rn/de.json
|
||||
- [x] **U-045 German Prev/Next read "Zurück"/"Weiter"** — `P3` — `bug` — override in locales/rn/de.json — fixed in 25f9b810
|
||||
- [ ] **Plain-text mail dark background fixed at #0a0a0a** — `P3` — `partial` — src/lib/email-html.ts:963 ignores the theme
|
||||
- [ ] **U-017 system alert vs Dialog, U-136 empty all-day strip, U-124 language order** — product calls, kept deferred unless you say otherwise
|
||||
- U-016 scrim token: won't fix (no visible change). U-018 font scale resets navigation: React Native limitation. U-131: done (776447dd).
|
||||
|
||||
@@ -171,7 +171,7 @@ RN toggles that are stored but never read (fix: either wire them or remove the c
|
||||
- What RN does: `setupPushNotificationsInner` calls `native.getToken()` directly (not the guarded `getFcmToken`) so a Firebase rejection (`fcm_token_failed`, typical on de-Googled devices or right after `deleteToken()` during Disable) bubbles up raw; `registerWithRelay` throws `Relay register failed: <status>` without the body (`RN: src/lib/push-notifications.ts:312-313,215-217`); `pollVerificationCode` times out after 75 s with the same message for every cause (`:268`). The UI shows `error.message` verbatim (`NotificationSettings.tsx:95-99`). Disable then immediately Enable also races: `teardownPushNotificationsForAccount` deletes the FCM token when it was the last account (`:477-481`) and the next `getToken()` may be rejected until Firebase re-registers.
|
||||
- Fix hint: read the relay response body into the error, distinguish "no Google Play services" (map to the UnifiedPush finding), skip `deleteToken()` unless the user disables push for all accounts, and log each phase (`token`, `relay`, `jmap create`, `verify`) so the settings pane can show which step failed. Needs a repro on the reporter's device to confirm the exact failure.
|
||||
|
||||
- [ ] **iOS push missing entirely** — `P2` — `missing` — deferred: needs an APNs transport in the relay + iOS token module; the settings pane now states Android-only
|
||||
- [x] **iOS push missing entirely** — `P2` — `missing` — deferred: needs an APNs transport in the relay + iOS token module; the settings pane now states Android-only — fixed in f62849d1
|
||||
- What WEB does: Web Push works on iOS 16.4+ once installed to the home screen (`lib/web-push.ts:157-164,394-398`).
|
||||
- What RN does: `getNative()` returns `null` off Android and setup throws "Push notifications require Android" (`RN: src/lib/push-notifications.ts:125-128,303-304`); the relay has only FCM and Web Push transports (`repos/relay/README.md` endpoints).
|
||||
- Fix hint: needs an APNs transport in the relay (`POST /api/push/register/apns` storing a device token, HTTP/2 to `api.push.apple.com` with a `.p8` key) plus an iOS native module (or `expo-notifications`) for the token and a Notification Service Extension to fetch sender/subject over JMAP. Content-blind payload stays the same.
|
||||
|
||||
@@ -231,7 +231,7 @@ RN's JMAP client (`src/api/jmap-client.ts`, 615 lines) is a thin transport: sess
|
||||
- What RN does: `onShouldStartLoadWithRequest` returns `true` for any `data:` URL (`src/components/EmailBodyView.tsx:715-718`), so `<a href="data:text/html,...">` replaces the message with attacker HTML rendered inside the app chrome (phishing form); the WebView is otherwise well hardened (`originWhitelist=['about:blank']`, file access off, `mixedContentMode="never"`, `setSupportMultipleWindows={false}`, `incognito`, `domStorageEnabled={false}`, `EmailBodyView.tsx:675-712`).
|
||||
- Fix hint: allow `data:` only for `request.isTopFrame === false` or block it entirely (the body is loaded via `source={{html}}`, not a data: URL).
|
||||
|
||||
- [ ] **No screenshot / recent-apps protection option** — `P3` — `missing`
|
||||
- [ ] **No screenshot / recent-apps protection option** — `P3` — `missing` — not done: needs a new native module (expo-screen-capture)
|
||||
- What WEB does: N/A.
|
||||
- What RN does: no `FLAG_SECURE`/`expo-screen-capture` anywhere; mail content appears in the Android recents thumbnail.
|
||||
- Fix hint: optional "Hide content in app switcher" setting using `expo-screen-capture` `preventScreenCaptureAsync`.
|
||||
|
||||
@@ -70,15 +70,15 @@ Stalwart 1.0.0 items found in the same pass (1.0 SSO scopes, Files on 1.0, #1385
|
||||
- What RN does: clearAllFeatureStores (`src/stores/auth-store.ts:126-134`) never clears `src/stores/search-history-store.ts:44`.
|
||||
- Fix hint: clear it, and check the other persisted stores in the same pass.
|
||||
|
||||
- [ ] **Internationalized domains** — `P3` — `missing`
|
||||
- [x] **Internationalized domains** — `P3` — `missing` — fixed in f5875f87 (sign-in, discovery, account rows; the composer's recipient check goes with the compose branch)
|
||||
- What WEB does: `lib/idn.ts:25` (#1100). What RN does: ASCII-only checks (`src/lib/recipients.ts:31`, `src/lib/server-discovery.ts:78`).
|
||||
- Fix hint: toASCII on the wire, Unicode on screen.
|
||||
|
||||
- [ ] **Sign-out doesn't end the identity provider session** — `P3` — `missing`
|
||||
- [ ] **Sign-out doesn't end the identity provider session** — `P3` — `missing` — not done: needs a decision; the app keeps the shared browser session for a first sign-in on purpose, an ephemeral session helps on iOS only, and end_session needs the id_token the app does not keep plus a registered post-logout redirect
|
||||
- What WEB does: `app/api/auth/token/route.ts:90` (#905). What RN does: `src/lib/oauth.ts:219`, `src/lib/oauth-native.ts:158`.
|
||||
- Fix hint: open end_session_endpoint, or use an ephemeral browser session.
|
||||
|
||||
- [ ] **Explain a refused token exchange on TOTP sign-in** — `P3` — `partial`
|
||||
- [x] **Explain a refused token exchange on TOTP sign-in** — `P3` — `partial` — fixed in 37e72c12
|
||||
- What WEB does: `stores/auth-store.ts:153`, `:1432`. What RN does: `src/lib/totp-login.ts:148`, `:156` throw token_exchange_failed; `src/lib/login-errors.ts:129` has no copy.
|
||||
|
||||
- [ ] **Shared account settings scope for calendar and contacts** — `P3` — `missing` (old 01 item)
|
||||
@@ -419,16 +419,16 @@ Stalwart 1.0.0 items found in the same pass (1.0 SSO scopes, Files on 1.0, #1385
|
||||
- What WEB does: Mailbox/AddressBook/Calendar shareWith in one batch (`lib/delegation/delegation.ts:96`).
|
||||
- What RN does: no mailbox sharing (`src/components/settings/FolderSettings.tsx`).
|
||||
|
||||
- [ ] **Shared accounts treated as capable when their capabilities say otherwise (#1239)** — `P3` — `bug`
|
||||
- [x] **Shared accounts treated as capable when their capabilities say otherwise (#1239)** — `P3` — `bug` — fixed in 67732348 (filters and auto-reply; contacts and files go with the calendar branch)
|
||||
- What WEB does: `lib/jmap/client.ts:6431`. What RN does: `src/api/contacts.ts:76`, `src/api/files.ts:98`, `src/api/sieve.ts:43`, `src/api/vacation.ts:77`.
|
||||
|
||||
- [ ] **Storage quota ignores softLimit (#1242)** — `P3` — `bug`
|
||||
- [x] **Storage quota ignores softLimit (#1242)** — `P3` — `bug` — fixed in af86339b
|
||||
- What WEB does: `lib/quota.ts:32`. What RN does: `src/api/quota.ts:50`.
|
||||
|
||||
- [ ] **Push extras: Inbox-only switch (#983), one ring per message (#1141), generic notification when the lookup fails (#1041), re-check on return (#1148)** — `P3` — `missing`/`partial`
|
||||
- [ ] **Push extras: Inbox-only switch (#983), one ring per message (#1141), generic notification when the lookup fails (#1041), re-check on return (#1148)** — `P3` — `missing`/`partial` — Inbox-only done in 1821fda7, generic notification in f6e6f8f5, re-check on return in 327b2cae; not done: one ring per message (#1141) needs a change in the Android notification builder (BulwarkFcmModule.kt) and a device
|
||||
- What RN does: `src/lib/notification-mutes.ts`, `src/lib/push-background-task.ts:468-488`, `:518`, `src/components/settings/NotificationSettings.tsx:115-134`.
|
||||
|
||||
- [ ] **Settings extras: language search, date locale override, font size everywhere, nav-bar icon style, sidebar apps never rendered, screenshot protection, Flat fields theme** — `P3` — `missing`/`partial` (old 08/09 items)
|
||||
- [ ] **Settings extras: language search, date locale override, font size everywhere, nav-bar icon style, sidebar apps never rendered, screenshot protection, Flat fields theme** — `P3` — `missing`/`partial` (old 08/09 items) — screenshot protection not done: needs a new native module (expo-screen-capture); text size widened in c9b3bdfe
|
||||
- What RN does: `src/components/settings/LanguageSettings.tsx:87`, `src/theme/dynamic.ts:48`, `src/lib/sidebar-apps.ts:10`, `src/theme/builtin-themes.ts:18`.
|
||||
|
||||
- [ ] **Search snippets, size filter, mail folder sharing, global search (1.10.0)** — `P3` — `missing`
|
||||
|
||||
@@ -130,22 +130,22 @@ Classification: `bug` = fixed in the webmail, still live in the app; `missing`;
|
||||
|
||||
### Accessibility
|
||||
|
||||
- [ ] **No Accessibility page or presets** — `P2` — `missing`
|
||||
- [x] **No Accessibility page or presets** — `P2` — `missing` — fixed in c9b3bdfe
|
||||
- What WEB does: Settings > Accessibility with presets (screen reader, low vision, dyslexia, focus, motor, motion, hearing) and text size 87.5% to 200% (`components/settings/accessibility-settings.tsx`, `lib/accessibility-presets.ts:23`, `lib/accessibility-settings.ts:13-22`; 8008393e5, 0731d24f6).
|
||||
- What RN does: no page (`src/screens/SettingsScreen.tsx:150-174`); text size 0.92/1/1.12 (`src/theme/dynamic.ts:14-18`). Text follows the system font scale; reduce motion follows the system (`src/theme/dynamic.ts:88-120`).
|
||||
- Fix hint: a page that groups the existing switches, widens the text size range and takes the presets that make sense on a phone.
|
||||
|
||||
- [ ] **Form fields have no name for screen readers** — `P2` — `bug`
|
||||
- [x] **Form fields have no name for screen readers** — `P2` — `bug` — fixed in ff5a8c91 (settings and sign-in; contact form and event editor are listed for their branches)
|
||||
- What WEB does: every field has a label tied to it (`components/ui/field.tsx:42`; ef13556a0, 0e2a05e0e, dcc21acd0, 4e2affe40, f73643bbb).
|
||||
- What RN does: the shared Input draws its label as separate Text (`src/components/Input.tsx:40-55`); the Security password and code fields have no label at all (`src/components/settings/AccountSecuritySettings.tsx:181`, `:191`, `:198`); contact fields rely on placeholders (`src/screens/ContactFormScreen.tsx:823-834`); the reminder field's only name is "30" (`src/components/calendar/EventModal.tsx:772`, `:817`).
|
||||
- Fix hint: Input passes its label as accessibilityLabel; every bare TextInput gets one.
|
||||
|
||||
- [ ] **Toasts and Undo are not read out on iOS; the Undo bar isn't read out anywhere** — `P2` — `bug`
|
||||
- [x] **Toasts and Undo are not read out on iOS; the Undo bar isn't read out anywhere** — `P2` — `bug` — fixed in d812caf0, 01dd7461
|
||||
- What WEB does: live regions and announcements for toasts, loads, new mail, saved drafts and offline (`lib/announce.ts:124`, `components/ui/toast.tsx:471`; 36d13b76b, e2fc2c750, 23813be9b, d713d31af, 4c1f24947).
|
||||
- What RN does: no `announceForAccessibility` call in src; ToastHost and OfflineBanner use accessibilityLiveRegion, which only Android honours (`src/components/ToastHost.tsx:194`, `src/components/OfflineBanner.tsx:33`); the Undo bar has neither (`src/components/UndoSnackbar.tsx:129-160`).
|
||||
- Fix hint: announce through AccessibilityInfo on iOS, live region on the Undo bar.
|
||||
|
||||
- [ ] **Grey text is below 4.5:1 contrast** — `P2` — `bug`
|
||||
- [x] **Grey text is below 4.5:1 contrast** — `P2` — `bug` — fixed in 11522995
|
||||
- What WEB does: every built-in theme at 4.5:1 or more, with a contrast test (`lib/builtin-themes.ts:16`, `lib/__tests__/theme-contrast.test.ts`; 9f6c7b99c).
|
||||
- What RN does: textMuted is `#94a3b8` on white in the light theme (2.56:1) and `#737373` in dark (4.18:1 on the background, 3.19:1 on surfaces) (`src/theme/tokens.ts:236`, `:140`); used in 456 places, also as placeholder colour.
|
||||
- Fix hint: adjust both, add a contrast test over tokens.ts and builtin-themes.ts.
|
||||
@@ -155,21 +155,21 @@ Classification: `bug` = fixed in the webmail, still live in the app; `missing`;
|
||||
- What RN does: month day cells read only the number and the dots are silent (`src/components/calendar/MonthView.tsx:199-240`); event blocks have no role, label or date (`src/components/calendar/EventBlock.tsx:72-104`).
|
||||
- Fix hint: label day cells with date and event count; event blocks get a button role and a label with title, date and time.
|
||||
|
||||
- [ ] **No reading fonts, text spacing, more contrast or underlined links** — `P3` — `missing`
|
||||
- [ ] **No reading fonts, text spacing, more contrast or underlined links** — `P3` — `missing` — not done: own feature, not in this run
|
||||
- What WEB does: Atkinson Hyperlegible Next, Lexend, OpenDyslexic, spacing settings, more contrast, underlined links (`lib/accessibility-settings.ts:53-68`; 8008393e5, da215b8c0).
|
||||
- What RN does: none of these settings (`src/stores/settings-store.ts`).
|
||||
- Fix hint: the dyslexia font and line spacing first.
|
||||
|
||||
- [ ] **No colour-blind palette and no names next to colours** — `P3` — `missing`
|
||||
- [ ] **No colour-blind palette and no names next to colours** — `P3` — `missing` — not done: own feature, not in this run
|
||||
- What WEB does: a colour-blind-safe palette, a tag's letter in its dot, the calendar's name on events (cf4d02b49, d5d7d39ef, 396290003).
|
||||
- What RN does: colours only (`src/components/calendar/EventBlock.tsx:72-104`, `src/components/calendar/MonthView.tsx:233-240`).
|
||||
|
||||
- [ ] **Toast and Undo times are fixed and short** — `P3` — `missing`
|
||||
- [x] **Toast and Undo times are fixed and short** — `P3` — `missing` — fixed in c9b3bdfe
|
||||
- What WEB does: one duration setting, 5 to 60 s or until closed (`lib/toast-duration.ts:7-15`; 8d2ebccf0, be5e2dbc5).
|
||||
- What RN does: 5 s, errors 10 s (`src/stores/toast-store.ts:44`), Undo 5 s (`src/components/UndoSnackbar.tsx:15`).
|
||||
- Fix hint: a duration setting and a longer default while a screen reader is on.
|
||||
|
||||
- [ ] **No plain language and no explanations for technical terms** — `P3` — `missing`
|
||||
- [ ] **No plain language and no explanations for technical terms** — `P3` — `missing` — not done: own feature, not in this run
|
||||
- What WEB does: simpler English and German wording (`i18n/plain-language.ts`; bb739c744, 9861af6be, 5ffecf692) and term explanations (`components/ui/term.tsx`; 724c45d6e).
|
||||
- What RN does: none (`src/i18n/index.ts:79-140`).
|
||||
|
||||
@@ -180,12 +180,12 @@ Classification: `bug` = fixed in the webmail, still live in the app; `missing`;
|
||||
|
||||
### Accounts and sign-in
|
||||
|
||||
- [ ] **The two-factor prompt shows as a red error (#1422)** — `P3` — `bug`
|
||||
- [x] **The two-factor prompt shows as a red error (#1422)** — `P3` — `bug` — fixed in b205c13a
|
||||
- What WEB does: an info box with role status, no shake (`app/(main)/[locale]/login/page.tsx:1393`; 141190708).
|
||||
- What RN does: TotpRequiredError sets a notice (`src/screens/LoginScreen.tsx:389-391`) shown in the error tone (`src/screens/login/PasswordStep.tsx:151`, `src/screens/login/LoginNotice.tsx:15-24`).
|
||||
- Fix hint: `tone="info"` for the TOTP step.
|
||||
|
||||
- [ ] **Two accounts with the same name look alike in the switcher** — `P3` — `bug`
|
||||
- [x] **Two accounts with the same name look alike in the switcher** — `P3` — `bug` — fixed in 505fbc59 (Settings > Account and the shared accountRowText; the switcher row in SidebarDrawer goes with the reader branch; the port waits for question 9)
|
||||
- What WEB does: the address becomes the title when another account shares the name (`lib/account-utils.ts:159`; 3bb9f7df8).
|
||||
- What RN does: name as title, address below (`src/components/SidebarDrawer.tsx:884-891`, `src/components/settings/AccountSettings.tsx:186`). Report question 9 (port in the label) is the same area.
|
||||
- Fix hint: port accountRowText.
|
||||
@@ -197,11 +197,11 @@ Classification: `bug` = fixed in the webmail, still live in the app; `missing`;
|
||||
|
||||
### Settings
|
||||
|
||||
- [ ] **The density sample shows previews while "Show preview text" is off** — `P3` — `bug`
|
||||
- [x] **The density sample shows previews while "Show preview text" is off** — `P3` — `bug` — fixed in c8f417dd
|
||||
- What WEB does: c166651d1 (`components/settings/appearance-settings.tsx:14-16`).
|
||||
- What RN does: showPreview comes from the density only (`src/components/settings/AppearanceSettings.tsx:11-15`, `:24-27`).
|
||||
|
||||
- [ ] **The identity editor closes without asking about unsaved changes** — `P3` — `bug`
|
||||
- [x] **The identity editor closes without asking about unsaved changes** — `P3` — `bug` — fixed in 7ecac4ab
|
||||
- What WEB does: identity, rule and Sieve editors ask (`components/settings/use-discard-changes.tsx`; 9d3ae2203).
|
||||
- What RN does: Close, Cancel and Back call closeEditor (`src/components/settings/IdentitySettings.tsx:122`, `:304`, `:311`, `:389`). FilterRuleModal already asks.
|
||||
|
||||
@@ -231,12 +231,12 @@ Classification: `bug` = fixed in the webmail, still live in the app; `missing`;
|
||||
|
||||
### i18n
|
||||
|
||||
- [ ] **No Thai or Estonian, and the vendored catalogs are stale** — `P3` — `missing`
|
||||
- [x] **No Thai or Estonian, and the vendored catalogs are stale** — `P3` — `missing` — fixed in d23af55e, e8bc54eb
|
||||
- What WEB does: 29 languages, Thai new in 1.19.0 (#1379, be52744a5; `i18n/routing.ts:15`).
|
||||
- What RN does: 27 (`src/i18n/index.ts:25-53`); `locales/et/common.json` is vendored but not registered; no `th`. German lacks 1283 of the webmail's keys (last sync 7c914183).
|
||||
- Fix hint: `node scripts/sync-locales.mjs`, register th and et, add overlays.
|
||||
|
||||
- [ ] **Some German phone strings still say "du"** — `P3` — `bug`
|
||||
- [x] **Some German phone strings still say "du"** — `P3` — `bug` — fixed in 25f9b810
|
||||
- What WEB does: formal everywhere (7b720dd22).
|
||||
- What RN does: 12 overlay strings (`locales/rn/de.json`, e.g. settings.privacy.*, settings.backup.description); 18 more in the stale vendored catalog, which the sync fixes.
|
||||
- Fix hint: rewrite with "Sie".
|
||||
|
||||
@@ -364,7 +364,7 @@ carries the commit that fixed it.
|
||||
- [x] **U-044 · P2 · PP (German, 1.3) · The toolbar overflows and pushes More off screen.** *(fixed in 975ed08)*
|
||||
- "Stern hinzufügen" is long (`d_de_thread`).
|
||||
- Fix: drop the toolbar labels when they don't fit; keep the a11y labels.
|
||||
- [ ] **U-045 · P3 · German · Prev/Next read "Zurück"/"Weiter".** *(deferred: the strings come from the vendored webmail catalog (locales/de/common.json); the fix belongs in the webmail's German translation)*
|
||||
- [x] **U-045 · P3 · German · Prev/Next read "Zurück"/"Weiter".** *(deferred: the strings come from the vendored webmail catalog (locales/de/common.json); the fix belongs in the webmail's German translation)* — fixed in 25f9b810
|
||||
- "Zurück" reads as Back. "Allen antworten" wraps in the bottom bar.
|
||||
- Fix: "Vorherige"/"Nächste" (locale), and a single line with shrinking text.
|
||||
- [x] **U-046 · P2 · all · The tag sheet doesn't scroll.** *(fixed in b59d00f)*
|
||||
@@ -510,7 +510,7 @@ carries the commit that fixed it.
|
||||
- [x] **U-132 · P2 · all · The password step's return keys don't move on.** *(fixed in ae1ef5b)*
|
||||
- Username has no `next`. With TOTP on, the password field shows Next and does nothing.
|
||||
- Where: `PasswordStep.tsx:56,78`.
|
||||
- [ ] **U-133 · P2 · all · There is no way out while "Signing in" hangs.** *(deferred: a cancel needs an abortable sign-in in the auth flow, which is more than a UI fix; see product questions)* Hardware back is
|
||||
- [x] **U-133 · P2 · all · There is no way out while "Signing in" hangs.** — fixed in 55d008a2 *(deferred: a cancel needs an abortable sign-in in the auth flow, which is more than a UI fix; see product questions)* Hardware back is
|
||||
swallowed. Where: `SigningInStep.tsx`, `LoginScreen.tsx:165`.
|
||||
|
||||
### Found in the re-walk
|
||||
|
||||
+1744
-221
File diff suppressed because it is too large
Load Diff
+1745
-222
File diff suppressed because it is too large
Load Diff
+1742
-219
File diff suppressed because it is too large
Load Diff
+1752
-229
File diff suppressed because it is too large
Load Diff
+1762
-239
File diff suppressed because it is too large
Load Diff
+2048
-525
File diff suppressed because it is too large
Load Diff
+1749
-226
File diff suppressed because it is too large
Load Diff
+1737
-214
File diff suppressed because it is too large
Load Diff
+1738
-215
File diff suppressed because it is too large
Load Diff
+1744
-221
File diff suppressed because it is too large
Load Diff
+1742
-219
File diff suppressed because it is too large
Load Diff
+1740
-217
File diff suppressed because it is too large
Load Diff
+1744
-221
File diff suppressed because it is too large
Load Diff
+1733
-210
File diff suppressed because it is too large
Load Diff
+1738
-215
File diff suppressed because it is too large
Load Diff
+1756
-233
File diff suppressed because it is too large
Load Diff
+1743
-220
File diff suppressed because it is too large
Load Diff
+1736
-213
File diff suppressed because it is too large
Load Diff
+1743
-220
File diff suppressed because it is too large
Load Diff
+1745
-222
File diff suppressed because it is too large
Load Diff
+1747
-224
File diff suppressed because it is too large
Load Diff
+57
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "الجدول",
|
||||
"three_days": "3 أيام"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "حذف هذا الحدث؟"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "إضافة وسم",
|
||||
"delete_confirm_mobile": "{count, plural, zero {حذف # جهة اتصال؟ لا يمكن التراجع عن ذلك.} one {حذف جهة اتصال واحدة؟ لا يمكن التراجع عن ذلك.} two {حذف جهتي اتصال؟ لا يمكن التراجع عن ذلك.} few {حذف # جهات اتصال؟ لا يمكن التراجع عن ذلك.} many {حذف # جهة اتصال؟ لا يمكن التراجع عن ذلك.} other {حذف # جهة اتصال؟ لا يمكن التراجع عن ذلك.}}",
|
||||
"move_to_address_book": "نقل إلى دفتر عناوين",
|
||||
"tag_failed": "فشل إضافة الوسم"
|
||||
"tag_failed": "فشل إضافة الوسم",
|
||||
"delete_confirm_title": "حذف جهات الاتصال"
|
||||
},
|
||||
"contact": "جهة اتصال",
|
||||
"create_hint_mobile": "اضغط مطولًا لإنشاء مجموعة بدلًا من ذلك",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "اضغط مطولًا على وسم لإعادة تسميته",
|
||||
"tag_name_placeholder": "اسم الوسم الجديد",
|
||||
"unnamed": "بلا اسم"
|
||||
"unnamed": "بلا اسم",
|
||||
"delete_confirm_title": "حذف جهة الاتصال"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "وسم",
|
||||
@@ -477,7 +482,9 @@
|
||||
"no_identity": "لا توجد هوية إرسال متاحة"
|
||||
},
|
||||
"view_dark_mode": "العرض في الوضع الداكن",
|
||||
"view_light_mode": "العرض في الوضع الفاتح"
|
||||
"view_light_mode": "العرض في الوضع الفاتح",
|
||||
"not_spam_short": "ليس مزعجًا",
|
||||
"spam_short": "مزعج"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "تُحذف المجلدات مع كل ما بداخلها. لا يمكن التراجع عن ذلك.",
|
||||
@@ -515,7 +522,12 @@
|
||||
"symlink": "رابط رمزي",
|
||||
"upload_failed": "{count, plural, one {تعذّر رفع ملف واحد} two {تعذّر رفع ملفين} few {تعذّر رفع # ملفات} other {تعذّر رفع # ملف}}",
|
||||
"upload_unavailable": "أداة اختيار الملفات غير مثبتة في هذا الإصدار. أعد بناء التطبيق لتفعيل الرفع.",
|
||||
"uploading_progress": "جارٍ الرفع {current}/{total} · {name}"
|
||||
"uploading_progress": "جارٍ الرفع {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "هل أنت متأكد أنك تريد حذف {count, plural, one {عنصر واحد} other {# عنصر}}؟ لا يمكن التراجع عن هذا.",
|
||||
"batch_delete_success": "{count, plural, one {تم حذف عنصر واحد} other {تم حذف # عنصر}}",
|
||||
"delete_confirm_message": "هل أنت متأكد أنك تريد حذف \"{name}\"؟ لا يمكن التراجع عن هذا.",
|
||||
"delete_confirm_title": "حذف المورد",
|
||||
"delete_success": "تم الحذف بنجاح"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -884,6 +896,10 @@
|
||||
"sync_add_failed": "تعذّرت المزامنة مع دفتر العناوين",
|
||||
"sync_remove_failed": "تعذّرت الإزالة من دفتر العناوين",
|
||||
"synced_badge": "متزامن عبر دفتر العناوين"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "إظهار الرسائل كرسائل منفردة بدلًا من تجميعها حسب المحادثة",
|
||||
"label": "تعطيل تجميع المحادثات"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -893,7 +909,9 @@
|
||||
"remove_action": "إزالة الإجراء",
|
||||
"remove_condition": "إزالة الشرط",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, zero {# سطر} one {سطر واحد} two {سطران} few {# أسطر} many {# سطرًا} other {# سطر}}"
|
||||
"line_count": "{count, plural, zero {# سطر} one {سطر واحد} two {سطران} few {# أسطر} many {# سطرًا} other {# سطر}}",
|
||||
"confirm_save": "تأكيد الحفظ",
|
||||
"save_warning": "سيؤدي الحفظ إلى استبدال أي قواعد مرئية. لا يمكن التراجع عن هذا. انقر على حفظ مرة أخرى للتأكيد."
|
||||
},
|
||||
"validation_header_name": "أدخل اسم ترويسة لا يحتوي إلا على الأحرف A-Z والأرقام والشرطات والشرطات السفلية، مثل X-Spam-Flag."
|
||||
},
|
||||
@@ -937,7 +955,8 @@
|
||||
"no_name": "(بلا اسم)",
|
||||
"primary": "أساسية",
|
||||
"save_failed": "فشل الحفظ",
|
||||
"text_signature": "توقيع بنص عادي"
|
||||
"text_signature": "توقيع بنص عادي",
|
||||
"title": "هويات الإرسال"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "وسوم ملوّنة لتنظيم بريدك.",
|
||||
@@ -1036,6 +1055,10 @@
|
||||
"up_distributor_desc": "هناك عدة تطبيقات موزّعة لـ UnifiedPush مثبتة - اختر التطبيق الذي سيسلّم الإشعارات.",
|
||||
"up_distributor_label": "الموزّع",
|
||||
"up_no_distributor": "لا يوجد تطبيق موزّع لـ UnifiedPush مثبت على هذا الجهاز. ثبّت واحدًا (مثل ntfy) وحاول مجددًا."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "تحليل دعوات البريد الإلكتروني",
|
||||
"invitation_parsing_desc": "اكتشاف دعوات التقويم في مرفقات البريد وإظهار إجراءات التقويم"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1173,7 +1196,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "الإضافات",
|
||||
"updates": "التحديثات"
|
||||
"updates": "التحديثات",
|
||||
"content_senders": "المحتوى والمرسلون",
|
||||
"downloads": "التنزيلات",
|
||||
"identities": "الهويات",
|
||||
"keywords": "الوسوم",
|
||||
"language": "اللغة والمنطقة والوقت",
|
||||
"layout": "التخطيط",
|
||||
"sidebar_apps": "تطبيقات الشريط الجانبي",
|
||||
"templates": "القوالب",
|
||||
"themes": "السمات"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "حذف \"{name}\" نهائيًا؟",
|
||||
@@ -1195,6 +1227,24 @@
|
||||
"warnings": {
|
||||
"date_format": "يجب أن تكون التواريخ بالصيغة \"YYYY-MM-DD\" أو \"YYYY-MM-DD HH:MM\""
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "كل دقيقة",
|
||||
"2m": "كل دقيقتين",
|
||||
"30s": "كل 30 ثانية",
|
||||
"5m": "كل 5 دقائق",
|
||||
"description": "عدد مرات حفظ المسودات تلقائيًا",
|
||||
"label": "فاصل الحفظ التلقائي"
|
||||
},
|
||||
"description": "إعداد خيارات كتابة البريد الإلكتروني",
|
||||
"title": "محرر الرسائل"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "متقدم",
|
||||
"appearance": "المظهر",
|
||||
"general": "عام",
|
||||
"privacy": "الخصوصية والأمان"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+56
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Agenda",
|
||||
"three_days": "3 dies"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Voleu suprimir aquest esdeveniment?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Afegeix una etiqueta",
|
||||
"delete_confirm_mobile": "{count, plural, one {Voleu suprimir # contacte? Això no es pot desfer.} other {Voleu suprimir # contactes? Això no es pot desfer.}}",
|
||||
"move_to_address_book": "Mou a una llibreta d'adreces",
|
||||
"tag_failed": "No s'ha pogut etiquetar"
|
||||
"tag_failed": "No s'ha pogut etiquetar",
|
||||
"delete_confirm_title": "Suprimeix els contactes"
|
||||
},
|
||||
"contact": "Contacte",
|
||||
"create_hint_mobile": "Manteniu premut per crear un grup",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Manteniu premuda una etiqueta per canviar-ne el nom",
|
||||
"tag_name_placeholder": "Nom de l'etiqueta nova",
|
||||
"unnamed": "Sense nom"
|
||||
"unnamed": "Sense nom",
|
||||
"delete_confirm_title": "Suprimeix el contacte"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Etiqueta",
|
||||
@@ -482,7 +487,9 @@
|
||||
"no_identity": "No hi ha cap identitat d'enviament disponible"
|
||||
},
|
||||
"view_dark_mode": "Mostra en mode fosc",
|
||||
"view_light_mode": "Mostra en mode clar"
|
||||
"view_light_mode": "Mostra en mode clar",
|
||||
"not_spam_short": "No és brossa",
|
||||
"spam_short": "Brossa"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Les carpetes se suprimeixen amb tot el que contenen. Això no es pot desfer.",
|
||||
@@ -520,7 +527,12 @@
|
||||
"symlink": "Enllaç simbòlic",
|
||||
"upload_failed": "{count, plural, one {No s'ha pogut pujar 1 fitxer} other {No s'han pogut pujar # fitxers}}",
|
||||
"upload_unavailable": "El selector de fitxers no està inclòs en aquesta compilació. Torneu a compilar l'aplicació per activar les pujades.",
|
||||
"uploading_progress": "S'està pujant {current}/{total} · {name}"
|
||||
"uploading_progress": "S'està pujant {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "Segur que voleu suprimir {count, plural, one {1 element} other {# elements}}? Això no es pot desfer.",
|
||||
"batch_delete_success": "{count, plural, one {1 element suprimit} other {# elements suprimits}}",
|
||||
"delete_confirm_message": "Segur que voleu suprimir «{name}»? Això no es pot desfer.",
|
||||
"delete_confirm_title": "Suprimeix el recurs",
|
||||
"delete_success": "Suprimit correctament"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -889,6 +901,10 @@
|
||||
"sync_add_failed": "No s'ha pogut sincronitzar amb la llibreta d'adreces",
|
||||
"sync_remove_failed": "No s'ha pogut eliminar de la llibreta d'adreces",
|
||||
"synced_badge": "Sincronitzat mitjançant la llibreta d'adreces"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "Mostra els correus com a missatges individuals en lloc d'agrupar-los per conversa",
|
||||
"label": "Desactiva l'agrupació en converses"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -898,7 +914,9 @@
|
||||
"remove_action": "Elimina l'acció",
|
||||
"remove_condition": "Elimina la condició",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# línia} other {# línies}}"
|
||||
"line_count": "{count, plural, one {# línia} other {# línies}}",
|
||||
"confirm_save": "Confirma el desament",
|
||||
"save_warning": "En desar se sobreescriuran totes les regles visuals. Això no es pot desfer. Torneu a fer clic a Desa per confirmar-ho."
|
||||
},
|
||||
"validation_header_name": "Introduïu un nom de capçalera que només contingui les lletres A-Z, xifres, guions i guions baixos, com ara X-Spam-Flag."
|
||||
},
|
||||
@@ -942,7 +960,8 @@
|
||||
"no_name": "(sense nom)",
|
||||
"primary": "principal",
|
||||
"save_failed": "No s'ha pogut desar",
|
||||
"text_signature": "Signatura en text sense format"
|
||||
"text_signature": "Signatura en text sense format",
|
||||
"title": "Identitats d'enviament"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Etiquetes de colors per organitzar el correu.",
|
||||
@@ -1041,6 +1060,10 @@
|
||||
"up_distributor_desc": "Hi ha diverses aplicacions distribuïdores d'UnifiedPush instal·lades: trieu la que ha d'entregar les notificacions.",
|
||||
"up_distributor_label": "Distribuïdor",
|
||||
"up_no_distributor": "No hi ha cap aplicació distribuïdora d'UnifiedPush instal·lada en aquest dispositiu. Instal·leu-ne una (per exemple, ntfy) i torneu-ho a provar."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "Analitza les invitacions per correu",
|
||||
"invitation_parsing_desc": "Detecta les invitacions de calendari als fitxers adjunts del correu i mostra les accions del calendari"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1178,7 +1201,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Connectors",
|
||||
"updates": "Actualitzacions"
|
||||
"updates": "Actualitzacions",
|
||||
"content_senders": "Contingut i remitents",
|
||||
"downloads": "Baixades",
|
||||
"identities": "Identitats",
|
||||
"keywords": "Etiquetes",
|
||||
"language": "Idioma, regió i hora",
|
||||
"layout": "Disposició",
|
||||
"sidebar_apps": "Aplicacions de la barra lateral",
|
||||
"templates": "Plantilles",
|
||||
"themes": "Temes"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "Voleu suprimir «{name}» permanentment?",
|
||||
@@ -1200,6 +1232,23 @@
|
||||
"warnings": {
|
||||
"date_format": "Les dates han de tenir el format \"YYYY-MM-DD\" o \"YYYY-MM-DD HH:MM\""
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Cada minut",
|
||||
"2m": "Cada 2 minuts",
|
||||
"30s": "Cada 30 segons",
|
||||
"5m": "Cada 5 minuts",
|
||||
"description": "Amb quina freqüència es desen automàticament els esborranys",
|
||||
"label": "Interval de desat automàtic"
|
||||
},
|
||||
"description": "Configureu les opcions de redacció de correu",
|
||||
"title": "Redactor"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Avançat",
|
||||
"appearance": "Aparença",
|
||||
"privacy": "Privadesa i seguretat"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+56
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Program",
|
||||
"three_days": "3 dny"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Odstranit tuto událost?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Přidat štítek",
|
||||
"delete_confirm_mobile": "{count, plural, one {Smazat # kontakt? Tuto akci nelze vrátit zpět.} few {Smazat # kontakty? Tuto akci nelze vrátit zpět.} many {Smazat # kontaktu? Tuto akci nelze vrátit zpět.} other {Smazat # kontaktů? Tuto akci nelze vrátit zpět.}}",
|
||||
"move_to_address_book": "Přesunout do adresáře",
|
||||
"tag_failed": "Štítek se nepodařilo přidat"
|
||||
"tag_failed": "Štítek se nepodařilo přidat",
|
||||
"delete_confirm_title": "Odstranit kontakty"
|
||||
},
|
||||
"contact": "Kontakt",
|
||||
"create_hint_mobile": "Dlouhým stisknutím vytvoříte skupinu",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Štítek přejmenujete dlouhým stisknutím",
|
||||
"tag_name_placeholder": "Název nového štítku",
|
||||
"unnamed": "Bez jména"
|
||||
"unnamed": "Bez jména",
|
||||
"delete_confirm_title": "Odstranit kontakt"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Štítek",
|
||||
@@ -482,7 +487,8 @@
|
||||
"no_identity": "Není k dispozici žádná identita pro odesílání"
|
||||
},
|
||||
"view_dark_mode": "Zobrazit v tmavém režimu",
|
||||
"view_light_mode": "Zobrazit ve světlém režimu"
|
||||
"view_light_mode": "Zobrazit ve světlém režimu",
|
||||
"not_spam_short": "Není spam"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Složky se smažou včetně veškerého obsahu. Tuto akci nelze vrátit zpět.",
|
||||
@@ -520,7 +526,12 @@
|
||||
"symlink": "Symbolický odkaz",
|
||||
"upload_failed": "{count, plural, one {1 soubor se nepodařilo nahrát} few {# soubory se nepodařilo nahrát} other {# souborů se nepodařilo nahrát}}",
|
||||
"upload_unavailable": "Výběr souborů není v tomto sestavení nainstalován. Pro povolení nahrávání aplikaci znovu sestavte.",
|
||||
"uploading_progress": "Nahrávání {current}/{total} · {name}"
|
||||
"uploading_progress": "Nahrávání {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "Opravdu chcete odstranit {count, plural, one {1 položku} few {# položky} other {# položek}}? Tuto akci nelze vrátit zpět.",
|
||||
"batch_delete_success": "{count, plural, one {Odstraněna 1 položka} few {Odstraněny # položky} other {Odstraněno # položek}}",
|
||||
"delete_confirm_message": "Opravdu chcete odstranit \"{name}\"? Tuto akci nelze vrátit zpět.",
|
||||
"delete_confirm_title": "Odstranit položku",
|
||||
"delete_success": "Úspěšně odstraněno"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -889,6 +900,10 @@
|
||||
"sync_add_failed": "Synchronizace do adresáře selhala",
|
||||
"sync_remove_failed": "Odebrání z adresáře selhalo",
|
||||
"synced_badge": "Synchronizováno přes adresář"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "Zobrazovat e-maily jako jednotlivé zprávy namísto seskupování do vláken",
|
||||
"label": "Zakázat seskupování konverzací"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -898,7 +913,9 @@
|
||||
"remove_action": "Odebrat akci",
|
||||
"remove_condition": "Odebrat podmínku",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# řádek} few {# řádky} many {# řádku} other {# řádků}}"
|
||||
"line_count": "{count, plural, one {# řádek} few {# řádky} many {# řádku} other {# řádků}}",
|
||||
"confirm_save": "Potvrdit uložení",
|
||||
"save_warning": "Uložení přepíše všechna vizuální pravidla. Tuto akci nelze vrátit zpět. Potvrďte opětovným kliknutím na Uložit."
|
||||
},
|
||||
"validation_header_name": "Zadejte název hlavičky, který obsahuje jen písmena A–Z, číslice, pomlčky a podtržítka, například X-Spam-Flag."
|
||||
},
|
||||
@@ -942,7 +959,8 @@
|
||||
"no_name": "(bez jména)",
|
||||
"primary": "hlavní",
|
||||
"save_failed": "Uložení selhalo",
|
||||
"text_signature": "Textový podpis"
|
||||
"text_signature": "Textový podpis",
|
||||
"title": "Identity odesílatele"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Barevné štítky pro organizaci pošty.",
|
||||
@@ -1041,6 +1059,10 @@
|
||||
"up_distributor_desc": "Je nainstalováno několik aplikací distributora UnifiedPush – vyberte tu, která má doručovat oznámení.",
|
||||
"up_distributor_label": "Distributor",
|
||||
"up_no_distributor": "V tomto zařízení není nainstalována žádná aplikace distributora UnifiedPush. Nainstalujte si ji (například ntfy) a zkuste to znovu."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "Rozpoznávat e-mailové pozvánky",
|
||||
"invitation_parsing_desc": "Rozpoznávat pozvánky kalendáře v přílohách e-mailů a zobrazovat akce kalendáře"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1178,7 +1200,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Pluginy",
|
||||
"updates": "Aktualizace"
|
||||
"updates": "Aktualizace",
|
||||
"content_senders": "Obsah a odesílatelé",
|
||||
"downloads": "Stažené",
|
||||
"identities": "Identity",
|
||||
"keywords": "Štítky",
|
||||
"language": "Jazyk, region a čas",
|
||||
"layout": "Vzhled",
|
||||
"sidebar_apps": "Aplikace postranního panelu",
|
||||
"templates": "Šablony",
|
||||
"themes": "Motivy"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "Trvale smazat „{name}“?",
|
||||
@@ -1200,6 +1231,24 @@
|
||||
"warnings": {
|
||||
"date_format": "Data musí být ve formátu „YYYY-MM-DD“ nebo „YYYY-MM-DD HH:MM“"
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Každou minutu",
|
||||
"2m": "Každé 2 minuty",
|
||||
"30s": "Každých 30 sekund",
|
||||
"5m": "Každých 5 minut",
|
||||
"description": "Jak často se mají koncepty automaticky ukládat",
|
||||
"label": "Interval automatického ukládání"
|
||||
},
|
||||
"description": "Nakonfigurujte nastavení pro psaní e-mailů",
|
||||
"title": "Psaní zpráv"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Pokročilé",
|
||||
"appearance": "Vzhled",
|
||||
"general": "Obecné",
|
||||
"privacy": "Soukromí a zabezpečení"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+53
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Tidsplan",
|
||||
"three_days": "3 dage"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Slet denne begivenhed?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Tilføj kategori",
|
||||
"delete_confirm_mobile": "{count, plural, one {Slet # kontakt? Dette kan ikke fortrydes.} other {Slet # kontakter? Dette kan ikke fortrydes.}}",
|
||||
"move_to_address_book": "Flyt til adressebog",
|
||||
"tag_failed": "Kunne ikke tildele kategori"
|
||||
"tag_failed": "Kunne ikke tildele kategori",
|
||||
"delete_confirm_title": "Slet kontakter"
|
||||
},
|
||||
"contact": "Kontakt",
|
||||
"create_hint_mobile": "Tryk længe for at oprette en gruppe i stedet",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Tryk længe på en kategori for at omdøbe den",
|
||||
"tag_name_placeholder": "Navn på ny kategori",
|
||||
"unnamed": "Unavngivet"
|
||||
"unnamed": "Unavngivet",
|
||||
"delete_confirm_title": "Slet kontakt"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Tag",
|
||||
@@ -479,7 +484,8 @@
|
||||
"no_identity": "Ingen afsenderidentitet tilgængelig"
|
||||
},
|
||||
"view_dark_mode": "Vis i mørk tilstand",
|
||||
"view_light_mode": "Vis i lys tilstand"
|
||||
"view_light_mode": "Vis i lys tilstand",
|
||||
"not_spam_short": "Ikke spam"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Mapper slettes sammen med alt deres indhold. Dette kan ikke fortrydes.",
|
||||
@@ -517,7 +523,12 @@
|
||||
"symlink": "Symbolsk link",
|
||||
"upload_failed": "{count, plural, one {1 fil kunne ikke uploades} other {# filer kunne ikke uploades}}",
|
||||
"upload_unavailable": "Filvælgeren er ikke installeret i denne build. Byg appen igen for at aktivere upload.",
|
||||
"uploading_progress": "Uploader {current}/{total} · {name}"
|
||||
"uploading_progress": "Uploader {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "Er du sikker på, at du vil slette {count, plural, one {1 element} other {# elementer}}? Dette kan ikke fortrydes.",
|
||||
"batch_delete_success": "{count, plural, one {1 element slettet} other {# elementer slettet}}",
|
||||
"delete_confirm_message": "Er du sikker på, at du vil slette \"{name}\"? Dette kan ikke fortrydes.",
|
||||
"delete_confirm_title": "Slet ressource",
|
||||
"delete_success": "Slettet"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -886,6 +897,10 @@
|
||||
"sync_add_failed": "Kunne ikke synkronisere til adressebogen",
|
||||
"sync_remove_failed": "Kunne ikke fjerne fra adressebogen",
|
||||
"synced_badge": "Synkroniseret via adressebog"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "Vis e-mails som individuelle beskeder i stedet for grupperet efter samtale",
|
||||
"label": "Deaktivér samtale-gruppering"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -895,7 +910,9 @@
|
||||
"remove_action": "Fjern handling",
|
||||
"remove_condition": "Fjern betingelse",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# linje} other {# linjer}}"
|
||||
"line_count": "{count, plural, one {# linje} other {# linjer}}",
|
||||
"confirm_save": "Bekræft gem",
|
||||
"save_warning": "Lagring vil overskrive eventuelle visuelle regler. Dette kan ikke fortrydes. Klik Gem igen for at bekræfte."
|
||||
},
|
||||
"validation_header_name": "Angiv et headernavn, der kun bruger bogstaverne A-Z, tal, bindestreger og understregninger, f.eks. X-Spam-Flag."
|
||||
},
|
||||
@@ -939,7 +956,8 @@
|
||||
"no_name": "(intet navn)",
|
||||
"primary": "primær",
|
||||
"save_failed": "Kunne ikke gemme",
|
||||
"text_signature": "Signatur i ren tekst"
|
||||
"text_signature": "Signatur i ren tekst",
|
||||
"title": "Afsenderidentiteter"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Farvede tags til at organisere din mail.",
|
||||
@@ -1038,6 +1056,10 @@
|
||||
"up_distributor_desc": "Der er installeret flere UnifiedPush-distributørapps - vælg den, der skal levere notifikationer.",
|
||||
"up_distributor_label": "Distributør",
|
||||
"up_no_distributor": "Der er ingen UnifiedPush-distributørapp installeret på denne enhed. Installer en (f.eks. ntfy), og prøv igen."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "Pars e-mail-invitationer",
|
||||
"invitation_parsing_desc": "Registrér kalenderinvitationer i e-mail-vedhæftninger og vis kalenderhandlinger"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1175,7 +1197,13 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Plugins",
|
||||
"updates": "Opdateringer"
|
||||
"updates": "Opdateringer",
|
||||
"content_senders": "Indhold & afsendere",
|
||||
"identities": "Identiteter",
|
||||
"language": "Sprog, region & tid",
|
||||
"sidebar_apps": "Sidepanel-apps",
|
||||
"templates": "Skabeloner",
|
||||
"themes": "Temaer"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "Slet \"{name}\" permanent?",
|
||||
@@ -1197,6 +1225,24 @@
|
||||
"warnings": {
|
||||
"date_format": "Datoer skal have formatet \"YYYY-MM-DD\" eller \"YYYY-MM-DD HH:MM\""
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Hvert minut",
|
||||
"2m": "Hvert 2. minut",
|
||||
"30s": "Hvert 30. sekund",
|
||||
"5m": "Hvert 5. minut",
|
||||
"description": "Hvor ofte kladder gemmes automatisk",
|
||||
"label": "Auto-gem interval"
|
||||
},
|
||||
"description": "Konfigurér indstillinger for e-mail-komposition",
|
||||
"title": "Skrivevindue"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Avanceret",
|
||||
"appearance": "Udseende",
|
||||
"general": "Generelt",
|
||||
"privacy": "Privatliv & sikkerhed"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+70
-21
@@ -143,6 +143,9 @@
|
||||
"n_days": "{count} Tage",
|
||||
"schedule": "Terminübersicht",
|
||||
"three_days": "3 Tage"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Dieses Ereignis löschen?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -169,7 +172,8 @@
|
||||
"add_tag": "Kategorie hinzufügen",
|
||||
"delete_confirm_mobile": "{count, plural, one {# Kontakt löschen? Dies kann nicht rückgängig gemacht werden.} other {# Kontakte löschen? Dies kann nicht rückgängig gemacht werden.}}",
|
||||
"move_to_address_book": "In Adressbuch verschieben",
|
||||
"tag_failed": "Kategorie konnte nicht zugewiesen werden"
|
||||
"tag_failed": "Kategorie konnte nicht zugewiesen werden",
|
||||
"delete_confirm_title": "Kontakte löschen"
|
||||
},
|
||||
"contact": "Kontakt",
|
||||
"create_hint_mobile": "Lange drücken, um stattdessen eine Gruppe zu erstellen",
|
||||
@@ -264,7 +268,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Tippen Sie lange auf eine Kategorie, um sie umzubenennen",
|
||||
"tag_name_placeholder": "Name der neuen Kategorie",
|
||||
"unnamed": "Unbenannt"
|
||||
"unnamed": "Unbenannt",
|
||||
"delete_confirm_title": "Kontakt löschen"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Label",
|
||||
@@ -461,11 +466,11 @@
|
||||
"pdf_external": "PDFs werden in Ihrem PDF-Viewer auf diesem Gerät geöffnet.",
|
||||
"unsupported": "Keine Vorschau für diesen Dateityp.",
|
||||
"media_unsupported": "Diese Datei kann hier nicht abgespielt werden.",
|
||||
"pdf_protected": "Diese PDF ist passwortgeschützt. Öffne sie in einer anderen App, um das Passwort einzugeben.",
|
||||
"pdf_protected": "Diese PDF ist passwortgeschützt. Öffnen Sie sie in einer anderen App, um das Passwort einzugeben.",
|
||||
"pdf_unreadable": "Diese PDF kann hier nicht angezeigt werden.",
|
||||
"archive_files": "{count, plural, one {# Datei} other {# Dateien}}",
|
||||
"archive_first_files": "Die ersten {count} Dateien",
|
||||
"archive_protected": "Dieses Archiv ist passwortgeschützt. Öffne es in einer anderen App, um das Passwort einzugeben.",
|
||||
"archive_protected": "Dieses Archiv ist passwortgeschützt. Öffnen Sie es in einer anderen App, um das Passwort einzugeben.",
|
||||
"archive_unreadable": "Dieses Archiv kann hier nicht geöffnet werden."
|
||||
},
|
||||
"received_via": "an {name}",
|
||||
@@ -494,7 +499,10 @@
|
||||
"no_identity": "Keine Absenderidentität verfügbar"
|
||||
},
|
||||
"view_dark_mode": "Im dunklen Modus anzeigen",
|
||||
"view_light_mode": "Im hellen Modus anzeigen"
|
||||
"view_light_mode": "Im hellen Modus anzeigen",
|
||||
"not_spam_short": "Kein Spam",
|
||||
"previous": "Vorherige",
|
||||
"next": "Nächste"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Ordner werden samt ihrem gesamten Inhalt gelöscht. Dies kann nicht rückgängig gemacht werden.",
|
||||
@@ -533,7 +541,12 @@
|
||||
"symlink": "Symbolischer Link",
|
||||
"upload_failed": "{count, plural, one {1 Datei konnte nicht hochgeladen werden} other {# Dateien konnten nicht hochgeladen werden}}",
|
||||
"upload_unavailable": "Die Dateiauswahl ist in diesem Build nicht installiert. Erstellen Sie die App neu, um Uploads zu aktivieren.",
|
||||
"uploading_progress": "Hochladen {current}/{total} · {name}"
|
||||
"uploading_progress": "Hochladen {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "Möchten Sie wirklich {count, plural, one {1 Element} other {# Elemente}} löschen? Dies kann nicht rückgängig gemacht werden.",
|
||||
"batch_delete_success": "{count, plural, one {1 Element gelöscht} other {# Elemente gelöscht}}",
|
||||
"delete_confirm_message": "Möchten Sie \"{name}\" wirklich löschen? Dies kann nicht rückgängig gemacht werden.",
|
||||
"delete_confirm_title": "Ressource löschen",
|
||||
"delete_success": "Erfolgreich gelöscht"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -907,6 +920,10 @@
|
||||
"sync_add_failed": "Synchronisierung mit dem Adressbuch fehlgeschlagen",
|
||||
"sync_remove_failed": "Entfernen aus dem Adressbuch fehlgeschlagen",
|
||||
"synced_badge": "Über Adressbuch synchronisiert"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "E-Mails als einzelne Nachrichten statt nach Konversation gruppiert anzeigen",
|
||||
"label": "Konversationsgruppierung deaktivieren"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -916,7 +933,9 @@
|
||||
"remove_action": "Aktion entfernen",
|
||||
"remove_condition": "Bedingung entfernen",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# Zeile} other {# Zeilen}}"
|
||||
"line_count": "{count, plural, one {# Zeile} other {# Zeilen}}",
|
||||
"confirm_save": "Speichern bestätigen",
|
||||
"save_warning": "Das Speichern überschreibt alle visuellen Regeln. Dies kann nicht rückgängig gemacht werden. Klicken Sie erneut auf Speichern zur Bestätigung."
|
||||
},
|
||||
"validation_header_name": "Geben Sie einen Header-Namen ein, der nur die Buchstaben A–Z, Ziffern, Bindestriche und Unterstriche enthält, zum Beispiel X-Spam-Flag."
|
||||
},
|
||||
@@ -960,7 +979,8 @@
|
||||
"no_name": "(kein Name)",
|
||||
"primary": "primär",
|
||||
"save_failed": "Speichern fehlgeschlagen",
|
||||
"text_signature": "Klartext-Signatur"
|
||||
"text_signature": "Klartext-Signatur",
|
||||
"title": "Sendeidentitäten"
|
||||
},
|
||||
"keywords": {
|
||||
"delete_confirm_title": "Tag „{label}“ löschen?",
|
||||
@@ -1060,6 +1080,10 @@
|
||||
"up_distributor_desc": "Mehrere UnifiedPush-Distributor-Apps sind installiert - wählen Sie die aus, die Benachrichtigungen zustellen soll.",
|
||||
"up_distributor_label": "Distributor",
|
||||
"up_no_distributor": "Auf diesem Gerät ist keine UnifiedPush-Distributor-App installiert. Installieren Sie eine (z. B. ntfy) und versuchen Sie es erneut."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "E-Mail-Einladungen erkennen",
|
||||
"invitation_parsing_desc": "Kalendereinladungen in E-Mail-Anhängen erkennen und Kalenderaktionen anzeigen"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1210,7 +1234,14 @@
|
||||
"updates": "Updates",
|
||||
"storage": "Speicher & Sicherung",
|
||||
"about": "Über",
|
||||
"troubleshooting": "Fehlerbehebung"
|
||||
"troubleshooting": "Fehlerbehebung",
|
||||
"content_senders": "Inhalte & Absender",
|
||||
"identities": "Identitäten",
|
||||
"keywords": "Labels",
|
||||
"language": "Sprache, Region & Zeit",
|
||||
"sidebar_apps": "Sidebar-Apps",
|
||||
"templates": "Vorlagen",
|
||||
"themes": "Designs"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "\"{name}\" endgültig löschen?",
|
||||
@@ -1240,19 +1271,19 @@
|
||||
"licences_desc": "AGPL-3.0 für die App und die Bibliotheken, die sie nutzt",
|
||||
"never_checked": "Noch nicht geprüft",
|
||||
"privacy": "Datenschutz",
|
||||
"privacy_desc": "Was auf diesem Telefon bleibt und was an deinen Server geht",
|
||||
"privacy_desc": "Was auf diesem Telefon bleibt und was an Ihren Server geht",
|
||||
"report_problem": "Problem melden",
|
||||
"report_problem_desc": "Öffnet ein Issue auf GitHub mit eingetragener Version",
|
||||
"server": "Server",
|
||||
"server_value": "{host} · angemeldet als {user}",
|
||||
"source_code": "Quellcode",
|
||||
"this_device": "Dieses Gerät",
|
||||
"this_device_desc": "Womit die App verbunden ist und wo du Hilfe bekommst.",
|
||||
"this_device_desc": "Womit die App verbunden ist und wo Sie Hilfe bekommen.",
|
||||
"up_to_date": "Aktuell",
|
||||
"updates": "Updates"
|
||||
},
|
||||
"backup": {
|
||||
"description": "Übertrage deine Einstellungen auf ein anderes Telefon oder ins Webmail.",
|
||||
"description": "Übertragen Sie Ihre Einstellungen auf ein anderes Telefon oder ins Webmail.",
|
||||
"export_import": "Exportieren oder importieren",
|
||||
"export_import_desc": "Eine JSON-Datei im selben Format wie im Webmail",
|
||||
"reset": "Zurücksetzen",
|
||||
@@ -1260,26 +1291,26 @@
|
||||
"title": "Einstellungen sichern"
|
||||
},
|
||||
"licences": {
|
||||
"app_desc": "Die App ist freie Software unter der {licence}. Du darfst sie nutzen, untersuchen, weitergeben und ändern.",
|
||||
"app_desc": "Die App ist freie Software unter der {licence}. Sie dürfen sie nutzen, untersuchen, weitergeben und ändern.",
|
||||
"libraries": "Bibliotheken",
|
||||
"libraries_desc": "Open-Source-Code, mit dem die App gebaut ist.",
|
||||
"read_licence": "Den vollständigen Lizenztext lesen"
|
||||
},
|
||||
"privacy": {
|
||||
"favicons": "Absendersymbole für geschäftliche Mails kommen vom Symboldienst von DuckDuckGo, der dabei die Domain des Absenders sieht. Ausschalten kannst du sie unter Darstellung → Absender-Favicons.",
|
||||
"favicons": "Absendersymbole für geschäftliche Mails kommen vom Symboldienst von DuckDuckGo, der dabei die Domain des Absenders sieht. Ausschalten können Sie sie unter Darstellung → Absender-Favicons.",
|
||||
"no_tracking": "Die App hat keine Analyse und keine Werbung.",
|
||||
"other_title": "Weitere Verbindungen",
|
||||
"phone_backup": "Nichts davon landet in der Cloud-Sicherung von Android.",
|
||||
"phone_secure": "Deine Anmeldedaten liegen im sicheren Speicher des Systems. Einstellungen, der Offline-Cache und noch nicht synchronisierte Änderungen liegen im Speicher der App.",
|
||||
"phone_secure": "Ihre Anmeldedaten liegen im sicheren Speicher des Systems. Einstellungen, der Offline-Cache und noch nicht synchronisierte Änderungen liegen im Speicher der App.",
|
||||
"phone_title": "Auf diesem Telefon",
|
||||
"push": "Wenn Benachrichtigungen an sind, meldet dein Server dem Push-Relay ({relay}), dass sich etwas geändert hat, und das Relay weckt die App über {service}. Das Relay sieht ein Gerätetoken und eine Änderungsmarke, nie deine Mails. Unter Benachrichtigungen kannst du ein eigenes Relay eintragen.",
|
||||
"push": "Wenn Benachrichtigungen an sind, meldet Ihr Server dem Push-Relay ({relay}), dass sich etwas geändert hat, und das Relay weckt die App über {service}. Das Relay sieht ein Gerätetoken und eine Änderungsmarke, nie Ihre Mails. Unter Benachrichtigungen können Sie ein eigenes Relay eintragen.",
|
||||
"push_title": "Push-Benachrichtigungen",
|
||||
"server": "Mails, Kalender, Kontakte und Dateien werden von deinem Server geladen und dort gespeichert. Außer den Verbindungen unten spricht die App nur mit deinem Server.",
|
||||
"server_title": "Dein Mailserver",
|
||||
"server": "Mails, Kalender, Kontakte und Dateien werden von Ihrem Server geladen und dort gespeichert. Außer den Verbindungen unten spricht die App nur mit Ihrem Server.",
|
||||
"server_title": "Ihr Mailserver",
|
||||
"service_apple": "den Push-Dienst von Apple",
|
||||
"service_google": "den Push-Dienst von Google",
|
||||
"updates": "Die App fragt bei GitHub nach der neuesten Version, um dir Updates anzuzeigen.",
|
||||
"updates_android": "Die App fragt bei GitHub nach der neuesten Version, um dir Updates anzuzeigen. Ausschalten kannst du das unter Updates."
|
||||
"updates": "Die App fragt bei GitHub nach der neuesten Version, um Ihnen Updates anzuzeigen.",
|
||||
"updates_android": "Die App fragt bei GitHub nach der neuesten Version, um Ihnen Updates anzuzeigen. Ausschalten können Sie das unter Updates."
|
||||
},
|
||||
"summary": {
|
||||
"events_off": "Termine aus",
|
||||
@@ -1293,7 +1324,7 @@
|
||||
"layout": "Schnellaktionen, Wischen, Reihenfolge",
|
||||
"reading": "Gelesen-Status, Löschen, Anhänge",
|
||||
"composing": "Signatur, Sendeverzögerung, Entwürfe",
|
||||
"identities": "Adressen, von denen du sendest",
|
||||
"identities": "Adressen, von denen Sie senden",
|
||||
"vacation": "Automatische Antwort bei Abwesenheit",
|
||||
"filters": "Regeln für eingehende Mails",
|
||||
"templates": "Wiederverwendbare Nachrichten",
|
||||
@@ -1312,6 +1343,24 @@
|
||||
"cache_title": "Zwischengespeicherte Daten",
|
||||
"row_desc": "Daten neu laden, Debug-Protokoll",
|
||||
"title": "Fehlerbehebung"
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Jede Minute",
|
||||
"2m": "Alle 2 Minuten",
|
||||
"30s": "Alle 30 Sekunden",
|
||||
"5m": "Alle 5 Minuten",
|
||||
"description": "Wie oft Entwürfe automatisch gespeichert werden",
|
||||
"label": "Automatisches Speichern"
|
||||
},
|
||||
"description": "Konfigurieren Sie die E-Mail-Erstellungseinstellungen",
|
||||
"title": "Editor"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Erweitert",
|
||||
"appearance": "Darstellung",
|
||||
"general": "Allgemein",
|
||||
"privacy": "Datenschutz & Sicherheit"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+84
-2
@@ -18,6 +18,7 @@
|
||||
"teal": "Teal"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Delete this event?",
|
||||
"location_hint": "Opens the location; long-press to copy it"
|
||||
},
|
||||
"discard": {
|
||||
@@ -204,6 +205,7 @@
|
||||
"bulk": {
|
||||
"add_tag": "Add tag",
|
||||
"delete_confirm_mobile": "{count, plural, one {Delete # contact? This cannot be undone.} other {Delete # contacts? This cannot be undone.}}",
|
||||
"delete_confirm_title": "Delete contacts",
|
||||
"move_to_address_book": "Move to address book",
|
||||
"tag_failed": "Tagging failed"
|
||||
},
|
||||
@@ -212,6 +214,7 @@
|
||||
"contact": "Contact",
|
||||
"create_hint_mobile": "Long-press to create a group instead",
|
||||
"delete_category": "Delete category",
|
||||
"delete_confirm_title": "Delete contact",
|
||||
"detail": {
|
||||
"add_to_group_failed": "Add to group failed",
|
||||
"copy_name": "{name} (Copy)",
|
||||
@@ -535,6 +538,8 @@
|
||||
"export_failed": "Export failed",
|
||||
"forwarded": "Forwarded",
|
||||
"load_failed": "Failed to load email",
|
||||
"next": "Next",
|
||||
"not_spam_short": "Not spam",
|
||||
"preview": {
|
||||
"archive_files": "{count, plural, one {# file} other {# files}}",
|
||||
"archive_first_files": "The first {count} files",
|
||||
@@ -547,6 +552,7 @@
|
||||
"pdf_unreadable": "This PDF can't be shown here.",
|
||||
"unsupported": "No preview for this file type."
|
||||
},
|
||||
"previous": "Prev",
|
||||
"quick_reply_login_switched": "This message is in another account. Switch back to it to reply.",
|
||||
"quick_reply_unconfirmed": "The server did not confirm the send. Check your Sent folder before sending again.",
|
||||
"received_via": "to {name}",
|
||||
@@ -567,6 +573,7 @@
|
||||
"show_less": "Show less",
|
||||
"smime_unsupported": "Signed/encrypted with S/MIME - not supported on mobile",
|
||||
"source_too_large": "The message is too large to display in full. Share the full source instead.",
|
||||
"spam_short": "Spam",
|
||||
"still_scheduled": "The message is still scheduled.",
|
||||
"still_scheduled_at": "The message is still scheduled for {date}.",
|
||||
"thread_loading": "Loading conversation...",
|
||||
@@ -578,9 +585,14 @@
|
||||
"view_light_mode": "View in light mode"
|
||||
},
|
||||
"files": {
|
||||
"batch_delete_confirm_message": "Are you sure you want to delete {count, plural, one {1 item} other {# items}}? This cannot be undone.",
|
||||
"batch_delete_success": "{count, plural, one {1 item deleted} other {# items deleted}}",
|
||||
"delete_cascade_hint": "Folders are deleted along with everything inside them. This can't be undone.",
|
||||
"delete_confirm_message": "Are you sure you want to delete \"{name}\"? This cannot be undone.",
|
||||
"delete_confirm_title": "Delete resource",
|
||||
"delete_item_title": "Delete this item?",
|
||||
"delete_items_title": "{count, plural, one {Delete # item?} other {Delete # items?}}",
|
||||
"delete_success": "Deleted successfully",
|
||||
"done": "Done",
|
||||
"download_failed_status": "Download failed (HTTP {status})",
|
||||
"error_forbidden": "You do not have permission to do this here.",
|
||||
@@ -649,6 +661,7 @@
|
||||
"camera_allow": "Allow camera access",
|
||||
"camera_disabled": "Camera access is disabled. Enable it in Settings to scan a sign-in QR code.",
|
||||
"camera_needed": "Bulwark Mail needs camera access to scan a sign-in QR code.",
|
||||
"cancel_sign_in": "Cancel sign-in",
|
||||
"choose_subtitle": "Choose how you'd like to sign in.",
|
||||
"confirm_check": "Check this looks right",
|
||||
"confirm_found": "Found your mail server",
|
||||
@@ -703,6 +716,7 @@
|
||||
"err_rate_title": "Too many attempts",
|
||||
"err_server_detail": "The server answered with an error. Try again in a few minutes.",
|
||||
"err_server_title": "{host} is having trouble",
|
||||
"err_token_exchange_title": "{host} didn't start a session",
|
||||
"err_totp_bad_detail": "Check your password and the current code from your authenticator app, then try again.",
|
||||
"err_totp_detail": "This account is protected with two-factor sign-in. Type the 6-digit code from your authenticator app.",
|
||||
"err_totp_title": "Enter your two-factor code",
|
||||
@@ -838,6 +852,31 @@
|
||||
"up_to_date": "Up to date",
|
||||
"updates": "Updates"
|
||||
},
|
||||
"accessibility": {
|
||||
"presets": {
|
||||
"applied_mobile": "{name} changed {count, plural, one {# setting} other {# settings}}",
|
||||
"apply": "Apply",
|
||||
"apply_named": "Apply {name}",
|
||||
"low-vision": {
|
||||
"description_mobile": "Text at 150% and labels under the toolbar icons."
|
||||
},
|
||||
"motion": {
|
||||
"description_mobile": "No animations or sliding transitions."
|
||||
},
|
||||
"screen-reader": {
|
||||
"description_mobile": "No conversation grouping, so every message is a row of its own."
|
||||
}
|
||||
},
|
||||
"reduce_motion": {
|
||||
"description_mobile": "Turns off sliding panels and transitions. The app also does this when reduced motion is on in your phone's settings."
|
||||
},
|
||||
"text_size": {
|
||||
"description_mobile": "Scales the app's text on top of your phone's text size. 100% is your phone's size."
|
||||
},
|
||||
"toast_duration": {
|
||||
"automatic": "Automatic (longer with a screen reader)"
|
||||
}
|
||||
},
|
||||
"account": {
|
||||
"accounts": {
|
||||
"description_mobile": "Reorder how accounts appear in the account switcher, pick the default, or sign one out.",
|
||||
@@ -864,6 +903,9 @@
|
||||
}
|
||||
},
|
||||
"appearance": {
|
||||
"accessibility_link": {
|
||||
"description_mobile": "Text size, reduced motion and the accessibility presets are on the Accessibility page."
|
||||
},
|
||||
"description_mobile": "Customize how the app looks",
|
||||
"language": {
|
||||
"restart_for_direction": "Restart the app to apply the new text direction.",
|
||||
@@ -887,6 +929,18 @@
|
||||
"not_on_server": "Not on this server",
|
||||
"unavailable": "Unavailable"
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Every minute",
|
||||
"2m": "Every 2 minutes",
|
||||
"30s": "Every 30 seconds",
|
||||
"5m": "Every 5 minutes",
|
||||
"description": "How often to save drafts automatically",
|
||||
"label": "Auto-save Interval"
|
||||
},
|
||||
"description": "Configure email composition settings",
|
||||
"title": "Composer"
|
||||
},
|
||||
"contacts": {
|
||||
"book_count": "{count, plural, one {# contact} other {# contacts}}",
|
||||
"default_book": "Default address book",
|
||||
@@ -1021,6 +1075,10 @@
|
||||
"description_mobile": "Replies always come from the identity a message was sent to. With this on, a reply to another address on your domains (a catch-all alias) is sent from that address too."
|
||||
},
|
||||
"content_senders_description_mobile": "Control how external resources and tracking pixels are handled.",
|
||||
"disable_threading": {
|
||||
"description": "Show emails as individual messages instead of grouped by conversation",
|
||||
"label": "Disable Conversation Grouping"
|
||||
},
|
||||
"external_content": {
|
||||
"allow_mobile": "Allow",
|
||||
"ask_mobile": "Ask",
|
||||
@@ -1063,7 +1121,9 @@
|
||||
"remove_action": "Remove action",
|
||||
"remove_condition": "Remove condition",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# line} other {# lines}}"
|
||||
"confirm_save": "Confirm Save",
|
||||
"line_count": "{count, plural, one {# line} other {# lines}}",
|
||||
"save_warning": "Saving will overwrite any visual rules. This cannot be undone. Click Save again to confirm."
|
||||
},
|
||||
"validation_header_name": "Enter a header name that uses only the letters A-Z, digits, hyphens and underscores, such as X-Spam-Flag."
|
||||
},
|
||||
@@ -1107,7 +1167,8 @@
|
||||
"no_name": "(no name)",
|
||||
"primary": "primary",
|
||||
"save_failed": "Save failed",
|
||||
"text_signature": "Plain-text signature"
|
||||
"text_signature": "Plain-text signature",
|
||||
"title": "Sending Identities"
|
||||
},
|
||||
"keywords": {
|
||||
"delete_confirm_title": "Delete tag \"{label}\"?",
|
||||
@@ -1168,6 +1229,10 @@
|
||||
"title": "Message list order"
|
||||
},
|
||||
"notifications": {
|
||||
"calendar": {
|
||||
"invitation_parsing": "Parse email invitations",
|
||||
"invitation_parsing_desc": "Detect calendar invitations in email attachments and show calendar actions"
|
||||
},
|
||||
"email": {
|
||||
"app_icon_badge": "Unread count on app icon",
|
||||
"app_icon_badge_desc": "Show the inbox unread count as a badge on the app icon.",
|
||||
@@ -1383,6 +1448,7 @@
|
||||
"mobile_explainer": "S/MIME is not available in the mobile app yet. Manage keys and certificates in the webmail; signed messages are shown without verification here."
|
||||
},
|
||||
"summary": {
|
||||
"accessibility": "Text size, motion, confirmations",
|
||||
"composing": "Signature, send delay, drafts",
|
||||
"contacts": "Address books, sorting",
|
||||
"content_senders": "Remote images, trusted senders",
|
||||
@@ -1408,10 +1474,25 @@
|
||||
"version": "Version {version}",
|
||||
"week_starts": "week starts {day}"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Advanced",
|
||||
"appearance": "Appearance",
|
||||
"general": "General",
|
||||
"privacy": "Privacy & Security"
|
||||
},
|
||||
"tabs": {
|
||||
"about": "About",
|
||||
"content_senders": "Content & Senders",
|
||||
"downloads": "Downloads",
|
||||
"identities": "Identities",
|
||||
"keywords": "Tags",
|
||||
"language": "Language, Region & Time",
|
||||
"layout": "Layout",
|
||||
"plugins": "Plugins",
|
||||
"sidebar_apps": "Sidebar Apps",
|
||||
"storage": "Storage & backup",
|
||||
"templates": "Templates",
|
||||
"themes": "Themes",
|
||||
"troubleshooting": "Troubleshooting",
|
||||
"updates": "Updates"
|
||||
},
|
||||
@@ -1421,6 +1502,7 @@
|
||||
"description_mobile": "Reusable snippets for common replies. Tap one to edit; long-press to delete.",
|
||||
"export_failed": "Export failed",
|
||||
"import_failed": "Import failed",
|
||||
"import_json_label": "Templates as JSON",
|
||||
"imported": "Imported",
|
||||
"imported_count": "{count, plural, one {# template} other {# templates}} added.",
|
||||
"is_html": "Body is HTML",
|
||||
|
||||
+55
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Agenda",
|
||||
"three_days": "3 días"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "¿Eliminar este evento?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Añadir etiqueta",
|
||||
"delete_confirm_mobile": "{count, plural, one {¿Eliminar # contacto? Esta acción no se puede deshacer.} other {¿Eliminar # contactos? Esta acción no se puede deshacer.}}",
|
||||
"move_to_address_book": "Mover a libreta de direcciones",
|
||||
"tag_failed": "No se pudo etiquetar"
|
||||
"tag_failed": "No se pudo etiquetar",
|
||||
"delete_confirm_title": "Eliminar contactos"
|
||||
},
|
||||
"contact": "Contacto",
|
||||
"create_hint_mobile": "Mantén pulsado para crear un grupo",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Mantén pulsada una etiqueta para cambiarle el nombre",
|
||||
"tag_name_placeholder": "Nombre de la nueva etiqueta",
|
||||
"unnamed": "Sin nombre"
|
||||
"unnamed": "Sin nombre",
|
||||
"delete_confirm_title": "Eliminar contacto"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Etiqueta",
|
||||
@@ -488,7 +493,8 @@
|
||||
"no_identity": "No hay ninguna identidad de envío disponible"
|
||||
},
|
||||
"view_dark_mode": "Ver en modo oscuro",
|
||||
"view_light_mode": "Ver en modo claro"
|
||||
"view_light_mode": "Ver en modo claro",
|
||||
"not_spam_short": "No spam"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Las carpetas se eliminan junto con todo su contenido. Esta acción no se puede deshacer.",
|
||||
@@ -526,7 +532,12 @@
|
||||
"symlink": "Enlace simbólico",
|
||||
"upload_failed": "{count, plural, one {No se pudo subir 1 archivo} other {No se pudieron subir # archivos}}",
|
||||
"upload_unavailable": "El selector de archivos no está incluido en esta compilación. Vuelve a compilar la app para habilitar las subidas.",
|
||||
"uploading_progress": "Subiendo {current}/{total} · {name}"
|
||||
"uploading_progress": "Subiendo {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "¿Está seguro de que desea eliminar {count, plural, one {1 elemento} other {# elementos}}? Esta acción no se puede deshacer.",
|
||||
"batch_delete_success": "{count, plural, one {1 elemento eliminado} other {# elementos eliminados}}",
|
||||
"delete_confirm_message": "¿Está seguro de que desea eliminar \"{name}\"? Esta acción no se puede deshacer.",
|
||||
"delete_confirm_title": "Eliminar recurso",
|
||||
"delete_success": "Eliminado correctamente"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -895,6 +906,10 @@
|
||||
"sync_add_failed": "No se pudo sincronizar con la libreta de direcciones",
|
||||
"sync_remove_failed": "No se pudo quitar de la libreta de direcciones",
|
||||
"synced_badge": "Sincronizado mediante la libreta de direcciones"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "Mostrar correos como mensajes individuales en lugar de agruparlos por conversación",
|
||||
"label": "Desactivar agrupación de conversaciones"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -904,7 +919,9 @@
|
||||
"remove_action": "Eliminar acción",
|
||||
"remove_condition": "Eliminar condición",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# línea} other {# líneas}}"
|
||||
"line_count": "{count, plural, one {# línea} other {# líneas}}",
|
||||
"confirm_save": "Confirmar guardado",
|
||||
"save_warning": "Guardar sobrescribirá todas las reglas visuales. Esta acción no se puede deshacer. Haga clic en Guardar de nuevo para confirmar."
|
||||
},
|
||||
"validation_header_name": "Escribe un nombre de encabezado que solo use las letras A-Z, números, guiones y guiones bajos, por ejemplo X-Spam-Flag."
|
||||
},
|
||||
@@ -948,7 +965,8 @@
|
||||
"no_name": "(sin nombre)",
|
||||
"primary": "principal",
|
||||
"save_failed": "No se pudo guardar",
|
||||
"text_signature": "Firma en texto sin formato"
|
||||
"text_signature": "Firma en texto sin formato",
|
||||
"title": "Identidades de Envío"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Etiquetas de colores para organizar tu correo.",
|
||||
@@ -1047,6 +1065,10 @@
|
||||
"up_distributor_desc": "Hay varias apps distribuidoras de UnifiedPush instaladas: elige la que debe entregar las notificaciones.",
|
||||
"up_distributor_label": "Distribuidor",
|
||||
"up_no_distributor": "No hay ninguna app distribuidora de UnifiedPush instalada en este dispositivo. Instala una (por ejemplo, ntfy) e inténtalo de nuevo."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "Analizar invitaciones por correo",
|
||||
"invitation_parsing_desc": "Detectar invitaciones de calendario en archivos adjuntos y mostrar acciones de calendario"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1184,7 +1206,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Plugins",
|
||||
"updates": "Actualizaciones"
|
||||
"updates": "Actualizaciones",
|
||||
"content_senders": "Contenido y remitentes",
|
||||
"downloads": "Descargas",
|
||||
"identities": "Identidades",
|
||||
"keywords": "Etiquetas",
|
||||
"language": "Idioma, Región y Hora",
|
||||
"layout": "Diseño",
|
||||
"sidebar_apps": "Apps de barra lateral",
|
||||
"templates": "Plantillas",
|
||||
"themes": "Temas"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "¿Eliminar \"{name}\" de forma permanente?",
|
||||
@@ -1206,6 +1237,23 @@
|
||||
"warnings": {
|
||||
"date_format": "Las fechas deben tener el formato \"YYYY-MM-DD\" o \"YYYY-MM-DD HH:MM\""
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Cada minuto",
|
||||
"2m": "Cada 2 minutos",
|
||||
"30s": "Cada 30 segundos",
|
||||
"5m": "Cada 5 minutos",
|
||||
"description": "Con qué frecuencia guardar borradores automáticamente",
|
||||
"label": "Intervalo de Guardado Automático"
|
||||
},
|
||||
"description": "Configure los ajustes de redacción de correos",
|
||||
"title": "Editor"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Avanzado",
|
||||
"appearance": "Apariencia",
|
||||
"privacy": "Privacidad y seguridad"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"calendar": {
|
||||
"detail": {
|
||||
"delete_confirm": "Kustutada see sündmus?"
|
||||
}
|
||||
},
|
||||
"contacts": {
|
||||
"bulk": {
|
||||
"delete_confirm_title": "Kustuta kontaktid"
|
||||
},
|
||||
"delete_confirm_title": "Kustuta kontakt"
|
||||
},
|
||||
"email_viewer": {
|
||||
"not_spam_short": "Pole rämpspost",
|
||||
"spam_short": "Rämpspost"
|
||||
},
|
||||
"files": {
|
||||
"batch_delete_confirm_message": "Kas oled kindel, et soovid kustutada {count, plural, one {1 üksuse} other {# üksust}}? Seda ei saa tagasi võtta.",
|
||||
"batch_delete_success": "{count, plural, one {1 üksus kustutatud} other {# üksust kustutatud}}",
|
||||
"delete_confirm_message": "Kas oled kindel, et soovid kustutada \"{name}\"? Seda ei saa tagasi võtta.",
|
||||
"delete_confirm_title": "Kustuta ressurss",
|
||||
"delete_success": "Edukalt kustutatud"
|
||||
},
|
||||
"settings": {
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Iga minuti järel",
|
||||
"2m": "Iga 2 minuti järel",
|
||||
"30s": "Iga 30 sekundi järel",
|
||||
"5m": "Iga 5 minuti järel",
|
||||
"description": "Kui sageli mustandeid automaatselt salvestatakse",
|
||||
"label": "Automaatsalvestuse intervall"
|
||||
},
|
||||
"description": "Seadista kirja koostamise sätteid",
|
||||
"title": "Koostamine"
|
||||
},
|
||||
"email_behavior": {
|
||||
"disable_threading": {
|
||||
"description": "Kuva e-kirjad üksikute kirjadena vestluste kaupa rühmitamise asemel",
|
||||
"label": "Keela vestluste rühmitamine"
|
||||
}
|
||||
},
|
||||
"filters": {
|
||||
"sieve_editor": {
|
||||
"confirm_save": "Kinnita salvestamine",
|
||||
"save_warning": "Salvestamine kirjutab kõik visuaalsed reeglid üle. Seda ei saa tagasi võtta. Kinnitamiseks klõpsa uuesti nuppu Salvesta."
|
||||
}
|
||||
},
|
||||
"identities": {
|
||||
"title": "Saatja identiteedid"
|
||||
},
|
||||
"notifications": {
|
||||
"calendar": {
|
||||
"invitation_parsing": "Tuvasta e-kirjade kutsed",
|
||||
"invitation_parsing_desc": "Tuvasta kalendrikutsed e-kirjade manustest ja kuva kalenditoimingud"
|
||||
}
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Täpsemalt",
|
||||
"appearance": "Välimus",
|
||||
"general": "Üldine",
|
||||
"privacy": "Privaatsus ja turvalisus"
|
||||
},
|
||||
"tabs": {
|
||||
"content_senders": "Sisu ja saatjad",
|
||||
"downloads": "Allalaadimised",
|
||||
"identities": "Identiteedid",
|
||||
"keywords": "Sildid",
|
||||
"language": "Keel, piirkond ja kellaaeg",
|
||||
"layout": "Paigutus",
|
||||
"sidebar_apps": "Külgriba rakendused",
|
||||
"templates": "Mallid",
|
||||
"themes": "Teemad"
|
||||
}
|
||||
}
|
||||
}
|
||||
+57
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "برنامه",
|
||||
"three_days": "۳ روز"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "این رویداد حذف شود؟"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "افزودن برچسب",
|
||||
"delete_confirm_mobile": "{count, plural, one {# مخاطب حذف شود؟ این کار قابل بازگشت نیست.} other {# مخاطب حذف شوند؟ این کار قابل بازگشت نیست.}}",
|
||||
"move_to_address_book": "انتقال به دفترچه آدرس",
|
||||
"tag_failed": "برچسبگذاری ناموفق بود"
|
||||
"tag_failed": "برچسبگذاری ناموفق بود",
|
||||
"delete_confirm_title": "حذف مخاطبین"
|
||||
},
|
||||
"contact": "مخاطب",
|
||||
"create_hint_mobile": "برای ساختن گروه بهجای آن، انگشت خود را نگه دارید",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "برای تغییر نام برچسب، انگشت خود را روی آن نگه دارید",
|
||||
"tag_name_placeholder": "نام جدید برچسب",
|
||||
"unnamed": "بدون نام"
|
||||
"unnamed": "بدون نام",
|
||||
"delete_confirm_title": "حذف مخاطب"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "برچسب",
|
||||
@@ -476,7 +481,9 @@
|
||||
"no_identity": "هیچ هویت ارسالی در دسترس نیست"
|
||||
},
|
||||
"view_dark_mode": "مشاهده در حالت تاریک",
|
||||
"view_light_mode": "مشاهده در حالت روشن"
|
||||
"view_light_mode": "مشاهده در حالت روشن",
|
||||
"not_spam_short": "هرزنامه نیست",
|
||||
"spam_short": "هرزنامه"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "پوشهها همراه با همه محتوایشان حذف میشوند. این کار قابل بازگشت نیست.",
|
||||
@@ -514,7 +521,12 @@
|
||||
"symlink": "پیوند نمادین",
|
||||
"upload_failed": "{count, plural, one {۱ فایل بارگذاری نشد} other {# فایل بارگذاری نشد}}",
|
||||
"upload_unavailable": "انتخابگر فایل در این نسخه نصب نشده است. برای فعال کردن بارگذاری، برنامه را دوباره بسازید.",
|
||||
"uploading_progress": "در حال بارگذاری {current}/{total} · {name}"
|
||||
"uploading_progress": "در حال بارگذاری {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "آیا از حذف {count, plural, one {۱ مورد} other {# مورد}} اطمینان دارید؟",
|
||||
"batch_delete_success": "{count, plural, one {۱ مورد حذف شد} other {# مورد حذف شد}}",
|
||||
"delete_confirm_message": "آیا از حذف \"{name}\" اطمینان دارید؟ این عملیات قابل بازگشت نیست.",
|
||||
"delete_confirm_title": "حذف منبع",
|
||||
"delete_success": "با موفقیت حذف شد"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -883,6 +895,10 @@
|
||||
"sync_add_failed": "همگامسازی با دفترچه آدرس ممکن نشد",
|
||||
"sync_remove_failed": "حذف از دفترچه آدرس ممکن نشد",
|
||||
"synced_badge": "همگامشده از طریق دفترچه آدرس"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "پیامهای جداگانه",
|
||||
"label": "غیرفعال کردن گروهبندی"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -892,7 +908,9 @@
|
||||
"remove_action": "حذف اقدام",
|
||||
"remove_condition": "حذف شرط",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# خط} other {# خط}}"
|
||||
"line_count": "{count, plural, one {# خط} other {# خط}}",
|
||||
"confirm_save": "تأیید ذخیره",
|
||||
"save_warning": "ذخیره قوانین بصری را بازنویسی میکند. این عملیات قابل بازگشت نیست. برای تأیید دوباره ذخیره را کلیک کنید."
|
||||
},
|
||||
"validation_header_name": "نام هدری وارد کنید که فقط از حروف A-Z، رقم، خط تیره و زیرخط تشکیل شده باشد، مانند X-Spam-Flag."
|
||||
},
|
||||
@@ -936,7 +954,8 @@
|
||||
"no_name": "(بدون نام)",
|
||||
"primary": "اصلی",
|
||||
"save_failed": "ذخیره ناموفق بود",
|
||||
"text_signature": "امضای متن ساده"
|
||||
"text_signature": "امضای متن ساده",
|
||||
"title": "هویتهای ارسال"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "برچسبهای رنگی برای سازماندهی ایمیلها.",
|
||||
@@ -1035,6 +1054,10 @@
|
||||
"up_distributor_desc": "چند برنامه توزیعکننده UnifiedPush نصب است - برنامهای را که باید اعلانها را تحویل دهد انتخاب کنید.",
|
||||
"up_distributor_label": "توزیعکننده",
|
||||
"up_no_distributor": "هیچ برنامه توزیعکننده UnifiedPush روی این دستگاه نصب نیست. یکی (مثلاً ntfy) نصب کنید و دوباره امتحان کنید."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "پردازش دعوتنامههای ایمیل",
|
||||
"invitation_parsing_desc": "تشخیص دعوتنامه در پیوست ایمیل"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1172,7 +1195,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "افزونهها",
|
||||
"updates": "بهروزرسانیها"
|
||||
"updates": "بهروزرسانیها",
|
||||
"content_senders": "محتوا و فرستندگان",
|
||||
"downloads": "دانلودها",
|
||||
"identities": "هویتها",
|
||||
"keywords": "برچسبها",
|
||||
"language": "زبان، منطقه و زمان",
|
||||
"layout": "چیدمان",
|
||||
"sidebar_apps": "برنامههای نوار کناری",
|
||||
"templates": "قالبها",
|
||||
"themes": "پوستهها"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "«{name}» برای همیشه حذف شود؟",
|
||||
@@ -1194,6 +1226,24 @@
|
||||
"warnings": {
|
||||
"date_format": "تاریخها باید بهصورت \"YYYY-MM-DD\" یا \"YYYY-MM-DD HH:MM\" باشند"
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "هر دقیقه",
|
||||
"2m": "هر ۲ دقیقه",
|
||||
"30s": "هر ۳۰ ثانیه",
|
||||
"5m": "هر ۵ دقیقه",
|
||||
"description": "فاصله ذخیره پیشنویس",
|
||||
"label": "ذخیره خودکار"
|
||||
},
|
||||
"description": "تنظیمات نوشتن ایمیل",
|
||||
"title": "ویرایشگر"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "پیشرفته",
|
||||
"appearance": "ظاهر",
|
||||
"general": "عمومی",
|
||||
"privacy": "حریم خصوصی و امنیت"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+56
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Planning",
|
||||
"three_days": "3 jours"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Supprimer cet événement ?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Ajouter une catégorie",
|
||||
"delete_confirm_mobile": "{count, plural, one {Supprimer # contact ? Cette action est irréversible.} other {Supprimer # contacts ? Cette action est irréversible.}}",
|
||||
"move_to_address_book": "Déplacer vers un carnet d'adresses",
|
||||
"tag_failed": "Échec de l'ajout de la catégorie"
|
||||
"tag_failed": "Échec de l'ajout de la catégorie",
|
||||
"delete_confirm_title": "Supprimer les contacts"
|
||||
},
|
||||
"contact": "Contact",
|
||||
"create_hint_mobile": "Appuyez longuement pour créer plutôt un groupe",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Appuyez longuement sur une catégorie pour la renommer",
|
||||
"tag_name_placeholder": "Nom de la nouvelle catégorie",
|
||||
"unnamed": "Sans nom"
|
||||
"unnamed": "Sans nom",
|
||||
"delete_confirm_title": "Supprimer le contact"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Étiquette",
|
||||
@@ -486,7 +491,8 @@
|
||||
"no_identity": "Aucune identité d'envoi disponible"
|
||||
},
|
||||
"view_dark_mode": "Afficher en mode sombre",
|
||||
"view_light_mode": "Afficher en mode clair"
|
||||
"view_light_mode": "Afficher en mode clair",
|
||||
"not_spam_short": "Non spam"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Les dossiers sont supprimés avec tout leur contenu. Cette action est irréversible.",
|
||||
@@ -524,7 +530,12 @@
|
||||
"symlink": "Lien symbolique",
|
||||
"upload_failed": "{count, plural, one {1 fichier n'a pas pu être téléversé} other {# fichiers n'ont pas pu être téléversés}}",
|
||||
"upload_unavailable": "Le sélecteur de fichiers n'est pas installé dans cette version. Recompilez l'application pour activer le téléversement.",
|
||||
"uploading_progress": "Téléversement {current}/{total} · {name}"
|
||||
"uploading_progress": "Téléversement {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "Êtes-vous sûr de vouloir supprimer {count, plural, one {1 élément} other {# éléments}} ? Cette action est irréversible.",
|
||||
"batch_delete_success": "{count, plural, one {1 élément supprimé} other {# éléments supprimés}}",
|
||||
"delete_confirm_message": "Êtes-vous sûr de vouloir supprimer \"{name}\" ? Cette action est irréversible.",
|
||||
"delete_confirm_title": "Supprimer la ressource",
|
||||
"delete_success": "Supprimé avec succès"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -893,6 +904,10 @@
|
||||
"sync_add_failed": "Impossible de synchroniser avec le carnet d'adresses",
|
||||
"sync_remove_failed": "Impossible de retirer du carnet d'adresses",
|
||||
"synced_badge": "Synchronisé via le carnet d'adresses"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "Afficher les e-mails comme messages individuels plutôt que groupés par conversation",
|
||||
"label": "Désactiver le regroupement par conversation"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -902,7 +917,9 @@
|
||||
"remove_action": "Supprimer l'action",
|
||||
"remove_condition": "Supprimer la condition",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# ligne} other {# lignes}}"
|
||||
"line_count": "{count, plural, one {# ligne} other {# lignes}}",
|
||||
"confirm_save": "Confirmer l'enregistrement",
|
||||
"save_warning": "L'enregistrement écrasera toutes les règles visuelles. Cette action est irréversible. Cliquez à nouveau sur Enregistrer pour confirmer."
|
||||
},
|
||||
"validation_header_name": "Saisissez un nom d'en-tête composé uniquement des lettres A à Z, de chiffres, de tirets et de tirets bas, par exemple X-Spam-Flag."
|
||||
},
|
||||
@@ -946,7 +963,8 @@
|
||||
"no_name": "(sans nom)",
|
||||
"primary": "principale",
|
||||
"save_failed": "Échec de l'enregistrement",
|
||||
"text_signature": "Signature en texte brut"
|
||||
"text_signature": "Signature en texte brut",
|
||||
"title": "Identités d'envoi"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Des étiquettes de couleur pour organiser vos e-mails.",
|
||||
@@ -1045,6 +1063,10 @@
|
||||
"up_distributor_desc": "Plusieurs applications de distribution UnifiedPush sont installées - choisissez celle qui doit distribuer les notifications.",
|
||||
"up_distributor_label": "Distributeur",
|
||||
"up_no_distributor": "Aucune application de distribution UnifiedPush n'est installée sur cet appareil. Installez-en une (par exemple ntfy) et réessayez."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "Analyser les invitations par e-mail",
|
||||
"invitation_parsing_desc": "Détecter les invitations de calendrier dans les pièces jointes et afficher les actions de calendrier"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1182,7 +1204,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Plugins",
|
||||
"updates": "Mises à jour"
|
||||
"updates": "Mises à jour",
|
||||
"content_senders": "Contenu et expéditeurs",
|
||||
"downloads": "Téléchargements",
|
||||
"identities": "Identités",
|
||||
"keywords": "Étiquettes",
|
||||
"language": "Langue, région et heure",
|
||||
"layout": "Mise en page",
|
||||
"sidebar_apps": "Apps de la barre latérale",
|
||||
"templates": "Modèles",
|
||||
"themes": "Thèmes"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "Supprimer définitivement « {name} » ?",
|
||||
@@ -1204,6 +1235,24 @@
|
||||
"warnings": {
|
||||
"date_format": "Les dates doivent être au format « YYYY-MM-DD » ou « YYYY-MM-DD HH:MM »"
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Toutes les minutes",
|
||||
"2m": "Toutes les 2 minutes",
|
||||
"30s": "Toutes les 30 secondes",
|
||||
"5m": "Toutes les 5 minutes",
|
||||
"description": "Fréquence de sauvegarde automatique des brouillons",
|
||||
"label": "Intervalle de sauvegarde automatique"
|
||||
},
|
||||
"description": "Configurez les paramètres de composition d'email",
|
||||
"title": "Rédaction"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Avancé",
|
||||
"appearance": "Apparence",
|
||||
"general": "Général",
|
||||
"privacy": "Confidentialité et sécurité"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+57
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "לוח זמנים",
|
||||
"three_days": "3 ימים"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "למחוק את האירוע הזה?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "הוסף תג",
|
||||
"delete_confirm_mobile": "{count, plural, one {למחוק איש קשר אחד? לא ניתן לבטל פעולה זו.} two {למחוק שני אנשי קשר? לא ניתן לבטל פעולה זו.} other {למחוק # אנשי קשר? לא ניתן לבטל פעולה זו.}}",
|
||||
"move_to_address_book": "העבר לספר כתובות",
|
||||
"tag_failed": "התיוג נכשל"
|
||||
"tag_failed": "התיוג נכשל",
|
||||
"delete_confirm_title": "מחק אנשי קשר"
|
||||
},
|
||||
"contact": "איש קשר",
|
||||
"create_hint_mobile": "לחיצה ארוכה ליצירת קבוצה במקום זאת",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "לחיצה ארוכה על תג לשינוי שמו",
|
||||
"tag_name_placeholder": "שם התג החדש",
|
||||
"unnamed": "ללא שם"
|
||||
"unnamed": "ללא שם",
|
||||
"delete_confirm_title": "מחק איש קשר"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "תג",
|
||||
@@ -480,7 +485,9 @@
|
||||
"no_identity": "אין זהות שליחה זמינה"
|
||||
},
|
||||
"view_dark_mode": "הצג במצב כהה",
|
||||
"view_light_mode": "הצג במצב בהיר"
|
||||
"view_light_mode": "הצג במצב בהיר",
|
||||
"not_spam_short": "לא דואר זבל",
|
||||
"spam_short": "דואר זבל"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "תיקיות נמחקות יחד עם כל מה שבתוכן. לא ניתן לבטל פעולה זו.",
|
||||
@@ -518,7 +525,12 @@
|
||||
"symlink": "קישור סמלי",
|
||||
"upload_failed": "{count, plural, one {לא ניתן היה להעלות קובץ אחד} other {לא ניתן היה להעלות # קבצים}}",
|
||||
"upload_unavailable": "בורר הקבצים אינו מותקן בגרסה זו. בנה מחדש את האפליקציה כדי לאפשר העלאות.",
|
||||
"uploading_progress": "מעלה {current}/{total} · {name}"
|
||||
"uploading_progress": "מעלה {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "האם למחוק {count, plural, one {פריט אחד} other {# פריטים}}? לא ניתן לבטל זאת.",
|
||||
"batch_delete_success": "{count, plural, one {פריט אחד נמחק} other {# פריטים נמחקו}}",
|
||||
"delete_confirm_message": "האם אתה בטוח שברצונך למחוק את \"{name}\"? לא ניתן לבטל זאת.",
|
||||
"delete_confirm_title": "מחק משאב",
|
||||
"delete_success": "נמחק בהצלחה"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -887,6 +899,10 @@
|
||||
"sync_add_failed": "לא ניתן היה לסנכרן לספר הכתובות",
|
||||
"sync_remove_failed": "לא ניתן היה להסיר מספר הכתובות",
|
||||
"synced_badge": "מסונכרן דרך ספר הכתובות"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "הצג הודעות אימייל כהודעות בודדות במקום מקובצות לפי שיחה",
|
||||
"label": "השבת את קיבוץ השיחות"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -896,7 +912,9 @@
|
||||
"remove_action": "הסר פעולה",
|
||||
"remove_condition": "הסר תנאי",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {שורה אחת} two {שתי שורות} other {# שורות}}"
|
||||
"line_count": "{count, plural, one {שורה אחת} two {שתי שורות} other {# שורות}}",
|
||||
"confirm_save": "אשר את השמירה",
|
||||
"save_warning": "שמירה תחליף את כל הכללים החזותיים. לא ניתן לבטל זאת. לחץ שוב על שמור כדי לאשר."
|
||||
},
|
||||
"validation_header_name": "הזן שם כותרת שכולל רק את האותיות A-Z, ספרות, מקפים וקווים תחתונים, למשל X-Spam-Flag."
|
||||
},
|
||||
@@ -940,7 +958,8 @@
|
||||
"no_name": "(ללא שם)",
|
||||
"primary": "ראשית",
|
||||
"save_failed": "השמירה נכשלה",
|
||||
"text_signature": "חתימה בטקסט רגיל"
|
||||
"text_signature": "חתימה בטקסט רגיל",
|
||||
"title": "שליחת זהויות"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "תגים צבעוניים לארגון הדואר שלך.",
|
||||
@@ -1039,6 +1058,10 @@
|
||||
"up_distributor_desc": "מותקנות כמה אפליקציות מפיץ של UnifiedPush - בחר את זו שתמסור את ההתראות.",
|
||||
"up_distributor_label": "מפיץ",
|
||||
"up_no_distributor": "לא מותקנת במכשיר זה אפליקציית מפיץ של UnifiedPush. התקן אחת (למשל ntfy) ונסה שוב."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "נתח הזמנות באימייל",
|
||||
"invitation_parsing_desc": "זיהוי הזמנות ליומן בקבצים מצורפים לאימייל והצג פעולות ביומן"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1176,7 +1199,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "תוספים",
|
||||
"updates": "עדכונים"
|
||||
"updates": "עדכונים",
|
||||
"content_senders": "תוכן ושולחים",
|
||||
"downloads": "הורדות",
|
||||
"identities": "זהויות",
|
||||
"keywords": "מילות מפתח",
|
||||
"language": "שפה ואזור",
|
||||
"layout": "פריסה",
|
||||
"sidebar_apps": "אפליקציות בסרגל הצד",
|
||||
"templates": "תבניות",
|
||||
"themes": "ערכות נושא"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "למחוק את \"{name}\" לצמיתות?",
|
||||
@@ -1198,6 +1230,24 @@
|
||||
"warnings": {
|
||||
"date_format": "תאריכים חייבים להיות בפורמט \"YYYY-MM-DD\" או \"YYYY-MM-DD HH:MM\""
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "כל דקה",
|
||||
"2m": "כל 2 דקות",
|
||||
"30s": "כל 30 שניות",
|
||||
"5m": "כל 5 דקות",
|
||||
"description": "באיזו תדירות לשמור טיוטות באופן אוטומטי",
|
||||
"label": "מרווח שמירה אוטומטית"
|
||||
},
|
||||
"description": "הגדר את הגדרות הרכב דוא\"ל",
|
||||
"title": "עורך"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "מתקדם",
|
||||
"appearance": "הופעה",
|
||||
"general": "כללי",
|
||||
"privacy": "פרטיות וביטחון"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+56
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Ütemezés",
|
||||
"three_days": "3 nap"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Törlöd ezt az eseményt?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Címke hozzáadása",
|
||||
"delete_confirm_mobile": "{count, plural, one {Törlöd a kijelölt # névjegyet? Ez nem vonható vissza.} other {Törlöd a kijelölt # névjegyet? Ez nem vonható vissza.}}",
|
||||
"move_to_address_book": "Áthelyezés címjegyzékbe",
|
||||
"tag_failed": "Nem sikerült a címkézés"
|
||||
"tag_failed": "Nem sikerült a címkézés",
|
||||
"delete_confirm_title": "Névjegyek törlése"
|
||||
},
|
||||
"contact": "Névjegy",
|
||||
"create_hint_mobile": "Nyomd hosszan, ha inkább csoportot hoznál létre",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Átnevezéshez nyomj hosszan egy címkét",
|
||||
"tag_name_placeholder": "Új címke neve",
|
||||
"unnamed": "Névtelen"
|
||||
"unnamed": "Névtelen",
|
||||
"delete_confirm_title": "Névjegy törlése"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Címke",
|
||||
@@ -478,7 +483,8 @@
|
||||
"no_identity": "Nincs elérhető küldő azonosság"
|
||||
},
|
||||
"view_dark_mode": "Megtekintés sötét módban",
|
||||
"view_light_mode": "Megtekintés világos módban"
|
||||
"view_light_mode": "Megtekintés világos módban",
|
||||
"not_spam_short": "Nem spam"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "A mappák a teljes tartalmukkal együtt törlődnek. Ez nem vonható vissza.",
|
||||
@@ -516,7 +522,12 @@
|
||||
"symlink": "Szimbolikus hivatkozás",
|
||||
"upload_failed": "{count, plural, one {1 fájlt nem sikerült feltölteni} other {# fájlt nem sikerült feltölteni}}",
|
||||
"upload_unavailable": "A fájlválasztó nincs telepítve ebben a buildben. A feltöltés engedélyezéséhez fordítsd újra az alkalmazást.",
|
||||
"uploading_progress": "Feltöltés: {current}/{total} · {name}"
|
||||
"uploading_progress": "Feltöltés: {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "Biztosan törölni szeretnéd {count, plural, one {az 1 elemet} other {a # elemet}}? Ez nem vonható vissza.",
|
||||
"batch_delete_success": "{count, plural, one {1 elem törölve} other {# elem törölve}}",
|
||||
"delete_confirm_message": "Biztosan törölni szeretnéd a \"{name}\" elemet? Ez nem vonható vissza.",
|
||||
"delete_confirm_title": "Erőforrás törlése",
|
||||
"delete_success": "Sikeresen törölve"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -885,6 +896,10 @@
|
||||
"sync_add_failed": "Nem sikerült szinkronizálni a címjegyzékkel",
|
||||
"sync_remove_failed": "Nem sikerült eltávolítani a címjegyzékből",
|
||||
"synced_badge": "A címjegyzéken keresztül szinkronizálva"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "E-mailek megjelenítése egyedi üzenetként ahelyett, hogy beszélgetésekbe lennének csoportosítva",
|
||||
"label": "Beszélgetéscsoportosítás letiltása"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -894,7 +909,9 @@
|
||||
"remove_action": "Művelet eltávolítása",
|
||||
"remove_condition": "Feltétel eltávolítása",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# sor} other {# sor}}"
|
||||
"line_count": "{count, plural, one {# sor} other {# sor}}",
|
||||
"confirm_save": "Mentés megerősítése",
|
||||
"save_warning": "A mentés felülír minden vizuális szabályt. Ez nem vonható vissza. Kattints újra a Mentés gombra a megerősítéshez."
|
||||
},
|
||||
"validation_header_name": "Adj meg egy fejlécnevet, amely csak az A–Z betűket, számjegyeket, kötőjeleket és aláhúzásjeleket tartalmazza, például X-Spam-Flag."
|
||||
},
|
||||
@@ -938,7 +955,8 @@
|
||||
"no_name": "(nincs név)",
|
||||
"primary": "elsődleges",
|
||||
"save_failed": "A mentés sikertelen",
|
||||
"text_signature": "Szöveges aláírás"
|
||||
"text_signature": "Szöveges aláírás",
|
||||
"title": "Küldő azonosságok"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Színes címkék a levelek rendszerezéséhez.",
|
||||
@@ -1037,6 +1055,10 @@
|
||||
"up_distributor_desc": "Több UnifiedPush-disztribútor alkalmazás is telepítve van – válaszd ki, melyik kézbesítse az értesítéseket.",
|
||||
"up_distributor_label": "Disztribútor",
|
||||
"up_no_distributor": "Ezen az eszközön nincs telepítve UnifiedPush-disztribútor alkalmazás. Telepíts egyet (például ntfy), és próbáld újra."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "E-mail meghívók feldolgozása",
|
||||
"invitation_parsing_desc": "Naptármeghívók észlelése az e-mail csatolmányokban és naptár műveletek megjelenítése"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1174,7 +1196,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Bővítmények",
|
||||
"updates": "Frissítések"
|
||||
"updates": "Frissítések",
|
||||
"content_senders": "Tartalom és feladók",
|
||||
"downloads": "Letöltések",
|
||||
"identities": "Azonosságok",
|
||||
"keywords": "Címkék",
|
||||
"language": "Nyelv, régió és idő",
|
||||
"layout": "Elrendezés",
|
||||
"sidebar_apps": "Oldalsáv alkalmazások",
|
||||
"templates": "Sablonok",
|
||||
"themes": "Témák"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "Véglegesen törlöd ezt: „{name}”?",
|
||||
@@ -1196,6 +1227,24 @@
|
||||
"warnings": {
|
||||
"date_format": "A dátumok formátuma „YYYY-MM-DD” vagy „YYYY-MM-DD HH:MM” legyen"
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Percre",
|
||||
"2m": "2 percenként",
|
||||
"30s": "30 másodpercenként",
|
||||
"5m": "5 percenként",
|
||||
"description": "Milyen gyakran mentse a piszkozatokat automatikusan",
|
||||
"label": "Automatikus mentés időköze"
|
||||
},
|
||||
"description": "E-mail írási beállítások konfigurálása",
|
||||
"title": "Szerkesztő"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Speciális",
|
||||
"appearance": "Megjelenés",
|
||||
"general": "Általános",
|
||||
"privacy": "Adatvédelem és biztonság"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+55
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Programma",
|
||||
"three_days": "3 giorni"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Eliminare questo evento?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Aggiungi etichetta",
|
||||
"delete_confirm_mobile": "{count, plural, one {Eliminare # contatto? Questa azione non può essere annullata.} other {Eliminare # contatti? Questa azione non può essere annullata.}}",
|
||||
"move_to_address_book": "Sposta nella rubrica",
|
||||
"tag_failed": "Impossibile applicare l'etichetta"
|
||||
"tag_failed": "Impossibile applicare l'etichetta",
|
||||
"delete_confirm_title": "Elimina contatti"
|
||||
},
|
||||
"contact": "Contatto",
|
||||
"create_hint_mobile": "Tieni premuto per creare un gruppo",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Tieni premuta un'etichetta per rinominarla",
|
||||
"tag_name_placeholder": "Nome della nuova etichetta",
|
||||
"unnamed": "Senza nome"
|
||||
"unnamed": "Senza nome",
|
||||
"delete_confirm_title": "Elimina contatto"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Etichetta",
|
||||
@@ -484,7 +489,8 @@
|
||||
"no_identity": "Nessuna identità di invio disponibile"
|
||||
},
|
||||
"view_dark_mode": "Visualizza in modalità scura",
|
||||
"view_light_mode": "Visualizza in modalità chiara"
|
||||
"view_light_mode": "Visualizza in modalità chiara",
|
||||
"not_spam_short": "Non spam"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Le cartelle vengono eliminate insieme a tutto il loro contenuto. L'operazione non può essere annullata.",
|
||||
@@ -522,7 +528,12 @@
|
||||
"symlink": "Collegamento simbolico",
|
||||
"upload_failed": "{count, plural, one {Impossibile caricare 1 file} other {Impossibile caricare # file}}",
|
||||
"upload_unavailable": "Il selettore di file non è installato in questa build. Ricompila l'app per abilitare i caricamenti.",
|
||||
"uploading_progress": "Caricamento {current}/{total} · {name}"
|
||||
"uploading_progress": "Caricamento {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "Sei sicuro di voler eliminare {count, plural, one {1 elemento} other {# elementi}}? Questa azione non può essere annullata.",
|
||||
"batch_delete_success": "{count, plural, one {1 elemento eliminato} other {# elementi eliminati}}",
|
||||
"delete_confirm_message": "Sei sicuro di voler eliminare \"{name}\"? Questa azione non può essere annullata.",
|
||||
"delete_confirm_title": "Elimina risorsa",
|
||||
"delete_success": "Eliminato con successo"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -891,6 +902,10 @@
|
||||
"sync_add_failed": "Impossibile sincronizzare con la rubrica",
|
||||
"sync_remove_failed": "Impossibile rimuovere dalla rubrica",
|
||||
"synced_badge": "Sincronizzato tramite la rubrica"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "Mostra le email come messaggi singoli anziché raggruppati per conversazione",
|
||||
"label": "Disabilita raggruppamento conversazioni"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -900,7 +915,9 @@
|
||||
"remove_action": "Rimuovi azione",
|
||||
"remove_condition": "Rimuovi condizione",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# riga} other {# righe}}"
|
||||
"line_count": "{count, plural, one {# riga} other {# righe}}",
|
||||
"confirm_save": "Conferma salvataggio",
|
||||
"save_warning": "Il salvataggio sovrascriverà tutte le regole visuali. Questa azione non può essere annullata. Fai clic su Salva di nuovo per confermare."
|
||||
},
|
||||
"validation_header_name": "Inserisci un nome di intestazione che usi solo le lettere A-Z, cifre, trattini e trattini bassi, ad esempio X-Spam-Flag."
|
||||
},
|
||||
@@ -944,7 +961,8 @@
|
||||
"no_name": "(nessun nome)",
|
||||
"primary": "principale",
|
||||
"save_failed": "Salvataggio non riuscito",
|
||||
"text_signature": "Firma in testo normale"
|
||||
"text_signature": "Firma in testo normale",
|
||||
"title": "Identità di invio"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Etichette colorate per organizzare la posta.",
|
||||
@@ -1043,6 +1061,10 @@
|
||||
"up_distributor_desc": "Sono installate più app distributore UnifiedPush - scegli quella che deve consegnare le notifiche.",
|
||||
"up_distributor_label": "Distributore",
|
||||
"up_no_distributor": "Nessuna app distributore UnifiedPush è installata su questo dispositivo. Installane una (ad esempio ntfy) e riprova."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "Analizza inviti via e-mail",
|
||||
"invitation_parsing_desc": "Rileva inviti calendario negli allegati e mostra azioni calendario"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1180,7 +1202,15 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Plugin",
|
||||
"updates": "Aggiornamenti"
|
||||
"updates": "Aggiornamenti",
|
||||
"content_senders": "Contenuto e mittenti",
|
||||
"downloads": "Download",
|
||||
"identities": "Identità",
|
||||
"keywords": "Etichette",
|
||||
"language": "Lingua, regione e ora",
|
||||
"sidebar_apps": "App nella barra laterale",
|
||||
"templates": "Modelli",
|
||||
"themes": "Temi"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "Eliminare definitivamente \"{name}\"?",
|
||||
@@ -1202,6 +1232,24 @@
|
||||
"warnings": {
|
||||
"date_format": "Le date devono essere nel formato \"YYYY-MM-DD\" o \"YYYY-MM-DD HH:MM\""
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Ogni minuto",
|
||||
"2m": "Ogni 2 minuti",
|
||||
"30s": "Ogni 30 secondi",
|
||||
"5m": "Ogni 5 minuti",
|
||||
"description": "Con quale frequenza salvare automaticamente le bozze",
|
||||
"label": "Intervallo di salvataggio automatico"
|
||||
},
|
||||
"description": "Configura le impostazioni di composizione email",
|
||||
"title": "Editor"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Avanzate",
|
||||
"appearance": "Aspetto",
|
||||
"general": "Generale",
|
||||
"privacy": "Privacy e sicurezza"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+57
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "スケジュール",
|
||||
"three_days": "3 日"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "このイベントを削除しますか?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "タグを追加",
|
||||
"delete_confirm_mobile": "{count, plural, other {#件の連絡先を削除しますか?この操作は元に戻せません。}}",
|
||||
"move_to_address_book": "アドレス帳に移動",
|
||||
"tag_failed": "タグを設定できませんでした"
|
||||
"tag_failed": "タグを設定できませんでした",
|
||||
"delete_confirm_title": "連絡先を削除"
|
||||
},
|
||||
"contact": "連絡先",
|
||||
"create_hint_mobile": "長押しするとグループを作成できます",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "タグを長押しすると名前を変更できます",
|
||||
"tag_name_placeholder": "新しいタグ名",
|
||||
"unnamed": "名前なし"
|
||||
"unnamed": "名前なし",
|
||||
"delete_confirm_title": "連絡先を削除"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "ラベル",
|
||||
@@ -482,7 +487,9 @@
|
||||
"no_identity": "利用できる送信者情報がありません"
|
||||
},
|
||||
"view_dark_mode": "ダークモードで表示",
|
||||
"view_light_mode": "ライトモードで表示"
|
||||
"view_light_mode": "ライトモードで表示",
|
||||
"not_spam_short": "迷惑メールでない",
|
||||
"spam_short": "迷惑メール"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "フォルダーは中身ごと削除されます。この操作は元に戻せません。",
|
||||
@@ -520,7 +527,12 @@
|
||||
"symlink": "シンボリックリンク",
|
||||
"upload_failed": "{count, plural, other {#件のファイルをアップロードできませんでした}}",
|
||||
"upload_unavailable": "このビルドにはファイルピッカーが含まれていません。アップロードを有効にするには、アプリを再ビルドしてください。",
|
||||
"uploading_progress": "アップロード中 {current}/{total} · {name}"
|
||||
"uploading_progress": "アップロード中 {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "{count, plural, other {#件のアイテム}}を削除してもよろしいですか?この操作は元に戻せません。",
|
||||
"batch_delete_success": "{count, plural, other {#件のアイテムを削除しました}}",
|
||||
"delete_confirm_message": "\"{name}\"を削除してもよろしいですか?この操作は元に戻せません。",
|
||||
"delete_confirm_title": "リソースを削除",
|
||||
"delete_success": "正常に削除しました"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -889,6 +901,10 @@
|
||||
"sync_add_failed": "アドレス帳と同期できませんでした",
|
||||
"sync_remove_failed": "アドレス帳から削除できませんでした",
|
||||
"synced_badge": "アドレス帳で同期済み"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "メールを会話ごとにグループ化せず、個別のメッセージとして表示します",
|
||||
"label": "会話グループ化を無効にする"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -898,7 +914,9 @@
|
||||
"remove_action": "アクションを削除",
|
||||
"remove_condition": "条件を削除",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, other {#行}}"
|
||||
"line_count": "{count, plural, other {#行}}",
|
||||
"confirm_save": "保存を確認",
|
||||
"save_warning": "保存するとすべてのビジュアルルールが上書きされます。この操作は元に戻せません。もう一度「保存」をクリックして確認してください。"
|
||||
},
|
||||
"validation_header_name": "ヘッダー名には英字(A~Z)、数字、ハイフン、アンダースコアのみを使用してください(例: X-Spam-Flag)。"
|
||||
},
|
||||
@@ -942,7 +960,8 @@
|
||||
"no_name": "(名前なし)",
|
||||
"primary": "プライマリ",
|
||||
"save_failed": "保存に失敗しました",
|
||||
"text_signature": "プレーンテキスト署名"
|
||||
"text_signature": "プレーンテキスト署名",
|
||||
"title": "送信者情報"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "メールを整理するためのカラーラベルです。",
|
||||
@@ -1041,6 +1060,10 @@
|
||||
"up_distributor_desc": "複数の UnifiedPush ディストリビューターアプリがインストールされています。通知の配信に使用するアプリを選択してください。",
|
||||
"up_distributor_label": "ディストリビューター",
|
||||
"up_no_distributor": "このデバイスには UnifiedPush ディストリビューターアプリがインストールされていません。アプリ(ntfy など)をインストールしてから、もう一度お試しください。"
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "メール招待を解析",
|
||||
"invitation_parsing_desc": "メール添付ファイルのカレンダー招待を検出し、カレンダーアクションを表示"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1178,7 +1201,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "プラグイン",
|
||||
"updates": "アップデート"
|
||||
"updates": "アップデート",
|
||||
"content_senders": "コンテンツと送信者",
|
||||
"downloads": "ダウンロード",
|
||||
"identities": "送信者情報",
|
||||
"keywords": "ラベル",
|
||||
"language": "言語、地域、時刻",
|
||||
"layout": "レイアウト",
|
||||
"sidebar_apps": "サイドバーアプリ",
|
||||
"templates": "テンプレート",
|
||||
"themes": "テーマ"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "「{name}」を完全に削除しますか?",
|
||||
@@ -1200,6 +1232,24 @@
|
||||
"warnings": {
|
||||
"date_format": "日付は「YYYY-MM-DD」または「YYYY-MM-DD HH:MM」の形式で入力してください"
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "1分ごと",
|
||||
"2m": "2分ごと",
|
||||
"30s": "30秒ごと",
|
||||
"5m": "5分ごと",
|
||||
"description": "下書きを自動保存する頻度",
|
||||
"label": "自動保存間隔"
|
||||
},
|
||||
"description": "メール作成の設定を構成",
|
||||
"title": "作成"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "詳細",
|
||||
"appearance": "外観",
|
||||
"general": "一般",
|
||||
"privacy": "プライバシーとセキュリティ"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+57
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "일정",
|
||||
"three_days": "3일"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "이 일정을 삭제할까요?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "태그 추가",
|
||||
"delete_confirm_mobile": "{count, plural, other {연락처 #개를 삭제할까요? 이 작업은 되돌릴 수 없어요.}}",
|
||||
"move_to_address_book": "주소록으로 이동",
|
||||
"tag_failed": "태그를 지정하지 못했어요"
|
||||
"tag_failed": "태그를 지정하지 못했어요",
|
||||
"delete_confirm_title": "연락처 삭제"
|
||||
},
|
||||
"contact": "연락처",
|
||||
"create_hint_mobile": "길게 누르면 대신 그룹을 만들 수 있어요",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "태그를 길게 누르면 이름을 바꿀 수 있어요",
|
||||
"tag_name_placeholder": "새 태그 이름",
|
||||
"unnamed": "이름 없음"
|
||||
"unnamed": "이름 없음",
|
||||
"delete_confirm_title": "연락처 삭제"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "태그",
|
||||
@@ -480,7 +485,9 @@
|
||||
"no_identity": "사용할 수 있는 보내는 사람 주소가 없어요"
|
||||
},
|
||||
"view_dark_mode": "다크 모드로 보기",
|
||||
"view_light_mode": "라이트 모드로 보기"
|
||||
"view_light_mode": "라이트 모드로 보기",
|
||||
"not_spam_short": "스팸 아님",
|
||||
"spam_short": "스팸"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "폴더를 삭제하면 안에 있는 모든 항목도 함께 삭제돼요. 이 작업은 되돌릴 수 없어요.",
|
||||
@@ -518,7 +525,12 @@
|
||||
"symlink": "심볼릭 링크",
|
||||
"upload_failed": "{count}개의 파일을 업로드하지 못했습니다",
|
||||
"upload_unavailable": "이 빌드에는 파일 선택기가 설치되어 있지 않아요. 업로드를 사용하려면 앱을 다시 빌드해 주세요.",
|
||||
"uploading_progress": "업로드 중 {current}/{total} · {name}"
|
||||
"uploading_progress": "업로드 중 {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "선택한 {count}개의 항목을 정말 삭제할까요? 이 작업은 되돌릴 수 없어요.",
|
||||
"batch_delete_success": "{count}개의 항목이 삭제되었어요",
|
||||
"delete_confirm_message": "정말 \"{name}\"을(를) 삭제할까요? 이 작업은 되돌릴 수 없어요.",
|
||||
"delete_confirm_title": "항목 삭제",
|
||||
"delete_success": "성공적으로 삭제되었어요"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -887,6 +899,10 @@
|
||||
"sync_add_failed": "주소록에 동기화할 수 없어요",
|
||||
"sync_remove_failed": "주소록에서 삭제할 수 없어요",
|
||||
"synced_badge": "주소록으로 동기화됨"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "관련된 메일을 그룹으로 묶지 않고 개별 메시지로 보여줘요",
|
||||
"label": "대화형 보기 끄기"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -896,7 +912,9 @@
|
||||
"remove_action": "동작 제거",
|
||||
"remove_condition": "조건 제거",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, other {#줄}}"
|
||||
"line_count": "{count, plural, other {#줄}}",
|
||||
"confirm_save": "저장 확인",
|
||||
"save_warning": "저장하면 시각적 규칙이 덮어쓰기 되며, 이 작업은 되돌릴 수 없어요. 확인을 위해 한 번 더 저장을 눌러주세요."
|
||||
},
|
||||
"validation_header_name": "헤더 이름은 영문자(A-Z), 숫자, 하이픈, 밑줄만 사용해 입력해 주세요(예: X-Spam-Flag)."
|
||||
},
|
||||
@@ -940,7 +958,8 @@
|
||||
"no_name": "(이름 없음)",
|
||||
"primary": "기본",
|
||||
"save_failed": "저장에 실패했어요",
|
||||
"text_signature": "일반 텍스트 서명"
|
||||
"text_signature": "일반 텍스트 서명",
|
||||
"title": "보내는 사람"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "메일을 정리하는 색상 태그예요.",
|
||||
@@ -1039,6 +1058,10 @@
|
||||
"up_distributor_desc": "여러 UnifiedPush 배포자 앱이 설치되어 있어요. 알림을 전달할 앱을 선택해 주세요.",
|
||||
"up_distributor_label": "배포자",
|
||||
"up_no_distributor": "이 기기에 UnifiedPush 배포자 앱이 설치되어 있지 않아요. 앱(예: ntfy)을 설치한 후 다시 시도해 주세요."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "이메일 초대장 분석",
|
||||
"invitation_parsing_desc": "이메일 첨부파일에서 캘린더 초대장을 감지하고 캘린더에 표시해요"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1176,7 +1199,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "플러그인",
|
||||
"updates": "업데이트"
|
||||
"updates": "업데이트",
|
||||
"content_senders": "콘텐츠 및 발신자",
|
||||
"downloads": "다운로드",
|
||||
"identities": "보내는 사람",
|
||||
"keywords": "태그",
|
||||
"language": "언어, 지역 및 시간",
|
||||
"layout": "레이아웃",
|
||||
"sidebar_apps": "사이드바 앱",
|
||||
"templates": "템플릿",
|
||||
"themes": "테마"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "\"{name}\"을(를) 영구 삭제할까요?",
|
||||
@@ -1198,6 +1230,24 @@
|
||||
"warnings": {
|
||||
"date_format": "날짜는 \"YYYY-MM-DD\" 또는 \"YYYY-MM-DD HH:MM\" 형식이어야 해요"
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "1분마다",
|
||||
"2m": "2분마다",
|
||||
"30s": "30초마다",
|
||||
"5m": "5분마다",
|
||||
"description": "작성 중인 메일을 임시보관함에 자동으로 저장하는 간격이에요",
|
||||
"label": "자동 저장 간격"
|
||||
},
|
||||
"description": "메일 작성과 관련된 설정을 확인해 보세요",
|
||||
"title": "메일 쓰기"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "고급",
|
||||
"appearance": "모양",
|
||||
"general": "일반",
|
||||
"privacy": "개인정보 및 보안"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+57
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Grafiks",
|
||||
"three_days": "3 dienas"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Vai dzēst šo pasākumu?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Pievienot tagu",
|
||||
"delete_confirm_mobile": "{count, plural, zero {Dzēst # kontaktus? To nevar atsaukt.} one {Dzēst # kontaktu? To nevar atsaukt.} other {Dzēst # kontaktus? To nevar atsaukt.}}",
|
||||
"move_to_address_book": "Pārvietot uz adrešu grāmatu",
|
||||
"tag_failed": "Neizdevās pievienot tagu"
|
||||
"tag_failed": "Neizdevās pievienot tagu",
|
||||
"delete_confirm_title": "Dzēst kontaktus"
|
||||
},
|
||||
"contact": "Kontakts",
|
||||
"create_hint_mobile": "Turiet nospiestu, lai tā vietā izveidotu grupu",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Turiet nospiestu tagu, lai to pārdēvētu",
|
||||
"tag_name_placeholder": "Jaunā taga nosaukums",
|
||||
"unnamed": "Bez vārda"
|
||||
"unnamed": "Bez vārda",
|
||||
"delete_confirm_title": "Dzēst kontaktu"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Tags",
|
||||
@@ -481,7 +486,9 @@
|
||||
"no_identity": "Nav pieejamas sūtīšanas identitātes"
|
||||
},
|
||||
"view_dark_mode": "Skatīt tumšajā režīmā",
|
||||
"view_light_mode": "Skatīt gaišajā režīmā"
|
||||
"view_light_mode": "Skatīt gaišajā režīmā",
|
||||
"not_spam_short": "Nav spams",
|
||||
"spam_short": "Spams"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Mapes tiek dzēstas kopā ar visu to saturu. To nevar atsaukt.",
|
||||
@@ -519,7 +526,12 @@
|
||||
"symlink": "Simboliskā saite",
|
||||
"upload_failed": "{count, plural, one {Neizdevās augšupielādēt # failu} other {Neizdevās augšupielādēt # failus}}",
|
||||
"upload_unavailable": "Failu atlasītājs šajā būvējumā nav instalēts. Lai iespējotu augšupielādi, pārbūvējiet lietotni.",
|
||||
"uploading_progress": "Augšupielādē {current}/{total} · {name}"
|
||||
"uploading_progress": "Augšupielādē {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "Vai tiešām vēlaties dzēst {count, plural, one {1 elementu} other {# elementus}}?",
|
||||
"batch_delete_success": "Izdzēsts {count, plural, one {1 elements} other {# elementi}}",
|
||||
"delete_confirm_message": "Vai tiešām vēlaties dzēst «{name}»? Šo darbību nevar atcelt.",
|
||||
"delete_confirm_title": "Dzēst resursu",
|
||||
"delete_success": "Sekmīgi izdzēsts"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -888,6 +900,10 @@
|
||||
"sync_add_failed": "Neizdevās sinhronizēt ar adrešu grāmatu",
|
||||
"sync_remove_failed": "Neizdevās noņemt no adrešu grāmatas",
|
||||
"synced_badge": "Sinhronizēts ar adrešu grāmatu"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "Rādīt vēstules kā atsevišķus ziņojumus, nevis sērijveidā",
|
||||
"label": "Izslēgt sarunu grupēšanu"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -897,7 +913,9 @@
|
||||
"remove_action": "Noņemt darbību",
|
||||
"remove_condition": "Noņemt nosacījumu",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, zero {# rindas} one {# rinda} other {# rindas}}"
|
||||
"line_count": "{count, plural, zero {# rindas} one {# rinda} other {# rindas}}",
|
||||
"confirm_save": "Apstiprināt saglabāšanu",
|
||||
"save_warning": "Saglabāšana pārrakstīs vizuālos noteikumus. To nevar atcelt. Nospiediet \"Saglabāt\" vēlreiz."
|
||||
},
|
||||
"validation_header_name": "Ievadiet galvenes nosaukumu, kurā ir tikai burti A–Z, cipari, defises un pasvītras, piemēram, X-Spam-Flag."
|
||||
},
|
||||
@@ -941,7 +959,8 @@
|
||||
"no_name": "(bez vārda)",
|
||||
"primary": "galvenā",
|
||||
"save_failed": "Saglabāšana neizdevās",
|
||||
"text_signature": "Teksta paraksts"
|
||||
"text_signature": "Teksta paraksts",
|
||||
"title": "Sūtītāja identitātes"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Krāsaini tagi pasta kārtošanai.",
|
||||
@@ -1040,6 +1059,10 @@
|
||||
"up_distributor_desc": "Ir instalētas vairākas UnifiedPush izplatītāja lietotnes – izvēlieties, kurai jāpiegādā paziņojumi.",
|
||||
"up_distributor_label": "Izplatītājs",
|
||||
"up_no_distributor": "Šajā ierīcē nav instalēta neviena UnifiedPush izplatītāja lietotne. Instalējiet kādu (piemēram, ntfy) un mēģiniet vēlreiz."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "Atpazīt uzaicinājumus e-pastā",
|
||||
"invitation_parsing_desc": "Noteikt kalendāra uzaicinājumus pielikumos un rādīt kalendāra darbības"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1177,7 +1200,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Spraudņi",
|
||||
"updates": "Atjauninājumi"
|
||||
"updates": "Atjauninājumi",
|
||||
"content_senders": "Saturs un sūtītāji",
|
||||
"downloads": "Lejupielādes",
|
||||
"identities": "Identitātes",
|
||||
"keywords": "Tagi",
|
||||
"language": "Valoda, reģions un laiks",
|
||||
"layout": "Izkārtojums",
|
||||
"sidebar_apps": "Sānu joslas lietotnes",
|
||||
"templates": "Veidnes",
|
||||
"themes": "Tēmas"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "Neatgriezeniski dzēst \"{name}\"?",
|
||||
@@ -1199,6 +1231,24 @@
|
||||
"warnings": {
|
||||
"date_format": "Datumiem jābūt formātā \"YYYY-MM-DD\" vai \"YYYY-MM-DD HH:MM\""
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Katru minūti",
|
||||
"2m": "Ik pēc 2 minūtēm",
|
||||
"30s": "Ik pēc 30 sekundēm",
|
||||
"5m": "Ik pēc 5 minūtēm",
|
||||
"description": "Cik bieži automātiski saglabāt melnrakstus",
|
||||
"label": "Automātiskās saglabāšanas intervāls"
|
||||
},
|
||||
"description": "Pielāgojiet vēstuļu rakstīšanas parametrus",
|
||||
"title": "Redaktors"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Papildu",
|
||||
"appearance": "Izskats",
|
||||
"general": "Vispārīgi",
|
||||
"privacy": "Privātums un drošība"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+57
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Хуваарь",
|
||||
"three_days": "3 өдөр"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Энэ үйл явдлыг устгах уу?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Таг нэмэх",
|
||||
"delete_confirm_mobile": "{count, plural, one {# харилцагчийг устгах уу? Үүнийг буцаах боломжгүй.} other {# харилцагчийг устгах уу? Үүнийг буцаах боломжгүй.}}",
|
||||
"move_to_address_book": "Хаягийн дэвтэр рүү зөөх",
|
||||
"tag_failed": "Таг нэмж чадсангүй"
|
||||
"tag_failed": "Таг нэмж чадсангүй",
|
||||
"delete_confirm_title": "Харилцагчдыг устгах"
|
||||
},
|
||||
"contact": "Харилцагч",
|
||||
"create_hint_mobile": "Оронд нь бүлэг үүсгэхийн тулд удаан дарна уу",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Тагийн нэрийг өөрчлөхийн тулд түүн дээр удаан дарна уу",
|
||||
"tag_name_placeholder": "Тагийн шинэ нэр",
|
||||
"unnamed": "Нэргүй"
|
||||
"unnamed": "Нэргүй",
|
||||
"delete_confirm_title": "Харилцагчийг устгах"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Таг",
|
||||
@@ -477,7 +482,9 @@
|
||||
"no_identity": "Илгээх таниулбар алга"
|
||||
},
|
||||
"view_dark_mode": "Харанхуй горимоор харах",
|
||||
"view_light_mode": "Цайвар горимоор харах"
|
||||
"view_light_mode": "Цайвар горимоор харах",
|
||||
"not_spam_short": "Спам биш",
|
||||
"spam_short": "Спам"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Хавтсыг доторх бүх зүйлийн хамт устгана. Үүнийг буцаах боломжгүй.",
|
||||
@@ -515,7 +522,12 @@
|
||||
"symlink": "Симбол холбоос",
|
||||
"upload_failed": "{count, plural, one {1 файлыг дээш ачаалж чадсангүй} other {# файлыг дээш ачаалж чадсангүй}}",
|
||||
"upload_unavailable": "Энэ хувилбарт файл сонгогч суулгаагүй байна. Байршуулалтыг идэвхжүүлэхийн тулд аппыг дахин бүтээнэ үү.",
|
||||
"uploading_progress": "Байршуулж байна {current}/{total} · {name}"
|
||||
"uploading_progress": "Байршуулж байна {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "{count, plural, one {1 элемент} other {# элемент}} устгахыг баталгаажуулж байна уу? Энэ үйлдлийг буцаах боломжгүй.",
|
||||
"batch_delete_success": "{count, plural, one {1 элемент устгагдлаа} other {# элемент устгагдлаа}}",
|
||||
"delete_confirm_message": "Та \"{name}\"-г устгахдаа итгэлтэй байна уу? Үүнийг буцаах боломжгүй.",
|
||||
"delete_confirm_title": "Нөөцийг устгах",
|
||||
"delete_success": "Амжилттай устгасан"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -884,6 +896,10 @@
|
||||
"sync_add_failed": "Хаягийн дэвтэртэй синк хийж чадсангүй",
|
||||
"sync_remove_failed": "Хаягийн дэвтрээс хасаж чадсангүй",
|
||||
"synced_badge": "Хаягийн дэвтрээр синк хийсэн"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "Имэйлүүдийг харилцан яриагаар бүлэглэхийн оронд тус тусад нь мессеж хэлбэрээр харуулах",
|
||||
"label": "Харилцааны бүлэглэлийг идэвхгүй болгох"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -893,7 +909,9 @@
|
||||
"remove_action": "Үйлдлийг хасах",
|
||||
"remove_condition": "Нөхцөлийг хасах",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# мөр} other {# мөр}}"
|
||||
"line_count": "{count, plural, one {# мөр} other {# мөр}}",
|
||||
"confirm_save": "Хадгалахыг баталгаажуулах",
|
||||
"save_warning": "Хадгалснаар ямар ч харааны дүрмийг дарж бичнэ. Үүнийг буцаах боломжгүй. Баталгаажуулахын тулд Save дахин дарна уу."
|
||||
},
|
||||
"validation_header_name": "Зөвхөн A-Z үсэг, тоо, зураас, доогуур зураас агуулсан толгойн нэр оруулна уу, жишээ нь X-Spam-Flag."
|
||||
},
|
||||
@@ -937,7 +955,8 @@
|
||||
"no_name": "(нэргүй)",
|
||||
"primary": "үндсэн",
|
||||
"save_failed": "Хадгалж чадсангүй",
|
||||
"text_signature": "Энгийн текст гарын үсэг"
|
||||
"text_signature": "Энгийн текст гарын үсэг",
|
||||
"title": "Танилцуулга илгээх"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Имэйлээ цэгцлэх өнгөт тагууд.",
|
||||
@@ -1036,6 +1055,10 @@
|
||||
"up_distributor_desc": "Хэд хэдэн UnifiedPush түгээгч апп суулгасан байна - мэдэгдэл хүргэх аппыг сонгоно уу.",
|
||||
"up_distributor_label": "Түгээгч",
|
||||
"up_no_distributor": "Энэ төхөөрөмжид UnifiedPush түгээгч апп суулгаагүй байна. Нэгийг суулгаад (жишээ нь ntfy) дахин оролдоно уу."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "Имэйл урилгыг задлан шинжлэх",
|
||||
"invitation_parsing_desc": "Имэйл хавсралтаас хуанлийн урилгыг илрүүлж, календарийн үйлдлийг харуулах"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1173,7 +1196,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Плагинууд",
|
||||
"updates": "Шинэчлэлт"
|
||||
"updates": "Шинэчлэлт",
|
||||
"content_senders": "Агуулга ба Илгээгчид",
|
||||
"downloads": "Татаж авах",
|
||||
"identities": "Тэмдэгтүүд",
|
||||
"keywords": "Тагууд",
|
||||
"language": "Хэл, бүс, цаг",
|
||||
"layout": "Байршил",
|
||||
"sidebar_apps": "Хажуугийн аппууд",
|
||||
"templates": "Загварууд",
|
||||
"themes": "Сэдэв"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "\"{name}\"-г бүрмөсөн устгах уу?",
|
||||
@@ -1195,6 +1227,24 @@
|
||||
"warnings": {
|
||||
"date_format": "Огноо \"YYYY-MM-DD\" эсвэл \"YYYY-MM-DD HH:MM\" хэлбэртэй байх ёстой"
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Минут тутам",
|
||||
"2m": "2 минут тутамд",
|
||||
"30s": "30 секунд тутамд",
|
||||
"5m": "5 минут тутамд",
|
||||
"description": "Ноорог автоматаар хэр олон удаа хадгалах вэ",
|
||||
"label": "Автоматаар хадгалах интервал"
|
||||
},
|
||||
"description": "Имэйл бичих тохиргоог тохируулах",
|
||||
"title": "Бичих цонх"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Дэвшилтэт",
|
||||
"appearance": "Гадаад төрх",
|
||||
"general": "Ерөнхий",
|
||||
"privacy": "Нууцлал ба Аюулгүй байдал"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+57
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Timeplan",
|
||||
"three_days": "3 dager"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Vil du slette denne hendelsen?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Legg til kategori",
|
||||
"delete_confirm_mobile": "{count, plural, one {Vil du slette # kontakt? Dette kan ikke angres.} other {Vil du slette # kontakter? Dette kan ikke angres.}}",
|
||||
"move_to_address_book": "Flytt til adressebok",
|
||||
"tag_failed": "Kunne ikke legge til kategorien"
|
||||
"tag_failed": "Kunne ikke legge til kategorien",
|
||||
"delete_confirm_title": "Slett kontakter"
|
||||
},
|
||||
"contact": "Kontakt",
|
||||
"create_hint_mobile": "Trykk lenge for å opprette en gruppe i stedet",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Trykk lenge på en kategori for å gi den nytt navn",
|
||||
"tag_name_placeholder": "Navn på ny kategori",
|
||||
"unnamed": "Uten navn"
|
||||
"unnamed": "Uten navn",
|
||||
"delete_confirm_title": "Slett kontakt"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Etikett",
|
||||
@@ -479,7 +484,9 @@
|
||||
"no_identity": "Ingen avsenderidentitet er tilgjengelig"
|
||||
},
|
||||
"view_dark_mode": "Vis i mørk modus",
|
||||
"view_light_mode": "Vis i lys modus"
|
||||
"view_light_mode": "Vis i lys modus",
|
||||
"not_spam_short": "Ikke søppelpost",
|
||||
"spam_short": "Søppelpost"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Mapper slettes sammen med alt innholdet. Dette kan ikke angres.",
|
||||
@@ -517,7 +524,12 @@
|
||||
"symlink": "Symbolsk lenke",
|
||||
"upload_failed": "{count, plural, one {1 fil kunne ikke lastes opp} other {# filer kunne ikke lastes opp}}",
|
||||
"upload_unavailable": "Filvelgeren er ikke installert i denne versjonen. Bygg appen på nytt for å aktivere opplasting.",
|
||||
"uploading_progress": "Laster opp {current}/{total} · {name}"
|
||||
"uploading_progress": "Laster opp {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "Er du sikker på at du vil slette {count, plural, one {1 element} other {# elementer}}? Dette kan ikke angres.",
|
||||
"batch_delete_success": "{count, plural, one {1 element slettet} other {# elementer slettet}}",
|
||||
"delete_confirm_message": "Er du sikker på at du vil slette «{name}»? Dette kan ikke angres.",
|
||||
"delete_confirm_title": "Slett fil eller mappe",
|
||||
"delete_success": "Elementet er slettet"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -886,6 +898,10 @@
|
||||
"sync_add_failed": "Kunne ikke synkronisere til adresseboken",
|
||||
"sync_remove_failed": "Kunne ikke fjerne fra adresseboken",
|
||||
"synced_badge": "Synkronisert via adresseboken"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "Vis e-poster som individuelle meldinger i stedet for gruppert etter samtale",
|
||||
"label": "Deaktiver samtalegruppering"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -895,7 +911,9 @@
|
||||
"remove_action": "Fjern handling",
|
||||
"remove_condition": "Fjern vilkår",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# linje} other {# linjer}}"
|
||||
"line_count": "{count, plural, one {# linje} other {# linjer}}",
|
||||
"confirm_save": "Bekreft lagring",
|
||||
"save_warning": "Lagring overskriver eventuelle visuelle regler. Dette kan ikke angres. Klikk på «Lagre» igjen for å bekrefte."
|
||||
},
|
||||
"validation_header_name": "Skriv inn et navn på meldingshode som bare bruker bokstavene A–Z, sifre, bindestreker og understreker, for eksempel X-Spam-Flag."
|
||||
},
|
||||
@@ -939,7 +957,8 @@
|
||||
"no_name": "(uten navn)",
|
||||
"primary": "hovedidentitet",
|
||||
"save_failed": "Kunne ikke lagre",
|
||||
"text_signature": "Signatur i ren tekst"
|
||||
"text_signature": "Signatur i ren tekst",
|
||||
"title": "Avsenderidentiteter"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Fargede etiketter for å organisere e-posten din.",
|
||||
@@ -1038,6 +1057,10 @@
|
||||
"up_distributor_desc": "Flere UnifiedPush-distributørapper er installert – velg den som skal levere varsler.",
|
||||
"up_distributor_label": "Distributør",
|
||||
"up_no_distributor": "Ingen UnifiedPush-distributørapp er installert på denne enheten. Installer en (for eksempel ntfy), og prøv igjen."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "Les kalenderinvitasjoner i e-post",
|
||||
"invitation_parsing_desc": "Finn kalenderinvitasjoner i e-postvedlegg og vis tilgjengelige kalenderhandlinger"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1175,7 +1198,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Programtillegg",
|
||||
"updates": "Oppdateringer"
|
||||
"updates": "Oppdateringer",
|
||||
"content_senders": "Innhold og avsendere",
|
||||
"downloads": "Nedlastinger",
|
||||
"identities": "Identiteter",
|
||||
"keywords": "Etiketter",
|
||||
"language": "Språk, region og tid",
|
||||
"layout": "Oppsett",
|
||||
"sidebar_apps": "Apper i sidefeltet",
|
||||
"templates": "Maler",
|
||||
"themes": "Temaer"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "Vil du slette «{name}» permanent?",
|
||||
@@ -1197,6 +1229,24 @@
|
||||
"warnings": {
|
||||
"date_format": "Datoer må ha formatet «YYYY-MM-DD» eller «YYYY-MM-DD HH:MM»"
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Hvert minutt",
|
||||
"2m": "Hvert 2. minutt",
|
||||
"30s": "Hvert 30. sekund",
|
||||
"5m": "Hvert 5. minutt",
|
||||
"description": "Hvor ofte utkast skal lagres automatisk",
|
||||
"label": "Intervall for automatisk lagring"
|
||||
},
|
||||
"description": "Velg innstillinger for skriving av e-post",
|
||||
"title": "Skriving"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Avansert",
|
||||
"appearance": "Utseende",
|
||||
"general": "Generelt",
|
||||
"privacy": "Personvern og sikkerhet"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+55
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Planning",
|
||||
"three_days": "3 dagen"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Dit evenement verwijderen?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Categorie toevoegen",
|
||||
"delete_confirm_mobile": "{count, plural, one {# contact verwijderen? Dit kan niet ongedaan worden gemaakt.} other {# contacten verwijderen? Dit kan niet ongedaan worden gemaakt.}}",
|
||||
"move_to_address_book": "Verplaatsen naar adresboek",
|
||||
"tag_failed": "Categorie toewijzen mislukt"
|
||||
"tag_failed": "Categorie toewijzen mislukt",
|
||||
"delete_confirm_title": "Contacten verwijderen"
|
||||
},
|
||||
"contact": "Contact",
|
||||
"create_hint_mobile": "Lang drukken om in plaats daarvan een groep te maken",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Druk lang op een categorie om deze te hernoemen",
|
||||
"tag_name_placeholder": "Naam nieuwe categorie",
|
||||
"unnamed": "Naamloos"
|
||||
"unnamed": "Naamloos",
|
||||
"delete_confirm_title": "Contact verwijderen"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Label",
|
||||
@@ -482,7 +487,8 @@
|
||||
"no_identity": "Geen verzendidentiteit beschikbaar"
|
||||
},
|
||||
"view_dark_mode": "Weergeven in donkere modus",
|
||||
"view_light_mode": "Weergeven in lichte modus"
|
||||
"view_light_mode": "Weergeven in lichte modus",
|
||||
"not_spam_short": "Geen spam"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Mappen worden samen met alles erin verwijderd. Dit kan niet ongedaan worden gemaakt.",
|
||||
@@ -520,7 +526,12 @@
|
||||
"symlink": "Symbolische koppeling",
|
||||
"upload_failed": "{count, plural, one {1 bestand kon niet worden geüpload} other {# bestanden konden niet worden geüpload}}",
|
||||
"upload_unavailable": "De bestandskiezer is niet geïnstalleerd in deze build. Bouw de app opnieuw om uploaden mogelijk te maken.",
|
||||
"uploading_progress": "Uploaden {current}/{total} · {name}"
|
||||
"uploading_progress": "Uploaden {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "Weet u zeker dat u {count, plural, one {1 item} other {# items}} wilt verwijderen? Dit kan niet ongedaan worden gemaakt.",
|
||||
"batch_delete_success": "{count, plural, one {1 item verwijderd} other {# items verwijderd}}",
|
||||
"delete_confirm_message": "Weet u zeker dat u \"{name}\" wilt verwijderen? Dit kan niet ongedaan worden gemaakt.",
|
||||
"delete_confirm_title": "Bron verwijderen",
|
||||
"delete_success": "Succesvol verwijderd"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -889,6 +900,10 @@
|
||||
"sync_add_failed": "Kon niet synchroniseren met het adresboek",
|
||||
"sync_remove_failed": "Kon niet verwijderen uit het adresboek",
|
||||
"synced_badge": "Gesynchroniseerd via adresboek"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "Toon e-mails als afzonderlijke berichten in plaats van gegroepeerd op gesprek",
|
||||
"label": "Conversatiegroepering uitschakelen"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -898,7 +913,9 @@
|
||||
"remove_action": "Actie verwijderen",
|
||||
"remove_condition": "Voorwaarde verwijderen",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# regel} other {# regels}}"
|
||||
"line_count": "{count, plural, one {# regel} other {# regels}}",
|
||||
"confirm_save": "Opslaan bevestigen",
|
||||
"save_warning": "Opslaan overschrijft alle visuele regels. Dit kan niet ongedaan worden gemaakt. Klik nogmaals op Opslaan om te bevestigen."
|
||||
},
|
||||
"validation_header_name": "Voer een headernaam in met alleen de letters A-Z, cijfers, koppeltekens en underscores, zoals X-Spam-Flag."
|
||||
},
|
||||
@@ -942,7 +959,8 @@
|
||||
"no_name": "(geen naam)",
|
||||
"primary": "primair",
|
||||
"save_failed": "Opslaan mislukt",
|
||||
"text_signature": "Handtekening in platte tekst"
|
||||
"text_signature": "Handtekening in platte tekst",
|
||||
"title": "Verzendidentiteiten"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Gekleurde labels om uw e-mail te organiseren.",
|
||||
@@ -1041,6 +1059,10 @@
|
||||
"up_distributor_desc": "Er zijn meerdere UnifiedPush-distributor-apps geïnstalleerd - kies de app die meldingen moet bezorgen.",
|
||||
"up_distributor_label": "Distributor",
|
||||
"up_no_distributor": "Er is geen UnifiedPush-distributor-app geïnstalleerd op dit apparaat. Installeer er een (bijvoorbeeld ntfy) en probeer het opnieuw."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "E-mailuitnodigingen herkennen",
|
||||
"invitation_parsing_desc": "Agenda-uitnodigingen in e-mailbijlagen detecteren en agendaacties tonen"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1178,7 +1200,15 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Plug-ins",
|
||||
"updates": "Updates"
|
||||
"updates": "Updates",
|
||||
"content_senders": "Inhoud en afzenders",
|
||||
"identities": "Identiteiten",
|
||||
"keywords": "Labels",
|
||||
"language": "Taal, Regio & Tijd",
|
||||
"layout": "Indeling",
|
||||
"sidebar_apps": "Zijbalk-apps",
|
||||
"templates": "Sjablonen",
|
||||
"themes": "Thema's"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "\"{name}\" definitief verwijderen?",
|
||||
@@ -1200,6 +1230,24 @@
|
||||
"warnings": {
|
||||
"date_format": "Datums moeten de vorm \"YYYY-MM-DD\" of \"YYYY-MM-DD HH:MM\" hebben"
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Elke minuut",
|
||||
"2m": "Elke 2 minuten",
|
||||
"30s": "Elke 30 seconden",
|
||||
"5m": "Elke 5 minuten",
|
||||
"description": "Hoe vaak concepten automatisch worden opgeslagen",
|
||||
"label": "Automatisch opslaan interval"
|
||||
},
|
||||
"description": "Configureer instellingen voor het opstellen van e-mails",
|
||||
"title": "Opstellen"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Geavanceerd",
|
||||
"appearance": "Weergave",
|
||||
"general": "Algemeen",
|
||||
"privacy": "Privacy en beveiliging"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+56
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Harmonogram",
|
||||
"three_days": "3 dni"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Usunąć to wydarzenie?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Dodaj etykietę",
|
||||
"delete_confirm_mobile": "{count, plural, one {Usunąć # kontakt? Tej operacji nie można cofnąć.} few {Usunąć # kontakty? Tej operacji nie można cofnąć.} many {Usunąć # kontaktów? Tej operacji nie można cofnąć.} other {Usunąć # kontaktu? Tej operacji nie można cofnąć.}}",
|
||||
"move_to_address_book": "Przenieś do książki adresowej",
|
||||
"tag_failed": "Nie udało się dodać etykiety"
|
||||
"tag_failed": "Nie udało się dodać etykiety",
|
||||
"delete_confirm_title": "Usuń kontakty"
|
||||
},
|
||||
"contact": "Kontakt",
|
||||
"create_hint_mobile": "Przytrzymaj, aby zamiast tego utworzyć grupę",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Przytrzymaj etykietę, aby zmienić jej nazwę",
|
||||
"tag_name_placeholder": "Nazwa nowej etykiety",
|
||||
"unnamed": "Bez nazwy"
|
||||
"unnamed": "Bez nazwy",
|
||||
"delete_confirm_title": "Usuń kontakt"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Etykieta",
|
||||
@@ -482,7 +487,8 @@
|
||||
"no_identity": "Brak tożsamości do wysyłki"
|
||||
},
|
||||
"view_dark_mode": "Wyświetl w trybie ciemnym",
|
||||
"view_light_mode": "Wyświetl w trybie jasnym"
|
||||
"view_light_mode": "Wyświetl w trybie jasnym",
|
||||
"not_spam_short": "Nie spam"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Foldery są usuwane wraz z całą zawartością. Tej operacji nie można cofnąć.",
|
||||
@@ -520,7 +526,12 @@
|
||||
"symlink": "Dowiązanie symboliczne",
|
||||
"upload_failed": "{count, plural, one {Nie udało się przesłać 1 pliku} few {Nie udało się przesłać # plików} many {Nie udało się przesłać # plików} other {Nie udało się przesłać # pliku}}",
|
||||
"upload_unavailable": "Selektor plików nie jest zainstalowany w tej wersji aplikacji. Przebuduj aplikację, aby włączyć przesyłanie.",
|
||||
"uploading_progress": "Przesyłanie {current}/{total} · {name}"
|
||||
"uploading_progress": "Przesyłanie {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "Czy na pewno chcesz usunąć {count, plural, one {1 element} other {# elementów}}? Tej operacji nie można cofnąć.",
|
||||
"batch_delete_success": "{count, plural, one {Usunięto 1 element} other {Usunięto # elementów}}",
|
||||
"delete_confirm_message": "Czy na pewno chcesz usunąć \"{name}\"? Tej operacji nie można cofnąć.",
|
||||
"delete_confirm_title": "Usuń zasób",
|
||||
"delete_success": "Usunięto pomyślnie"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -889,6 +900,10 @@
|
||||
"sync_add_failed": "Nie udało się zsynchronizować z książką adresową",
|
||||
"sync_remove_failed": "Nie udało się usunąć z książki adresowej",
|
||||
"synced_badge": "Synchronizowany przez książkę adresową"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "Pokazuj wiadomości e-mail jako pojedyncze wiadomości zamiast grupowania według konwersacji",
|
||||
"label": "Wyłącz grupowanie konwersacji"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -898,7 +913,9 @@
|
||||
"remove_action": "Usuń akcję",
|
||||
"remove_condition": "Usuń warunek",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# wiersz} few {# wiersze} many {# wierszy} other {# wiersza}}"
|
||||
"line_count": "{count, plural, one {# wiersz} few {# wiersze} many {# wierszy} other {# wiersza}}",
|
||||
"confirm_save": "Potwierdź zapis",
|
||||
"save_warning": "Zapisanie nadpisze wszystkie reguły wizualne. Nie można tego cofnąć. Kliknij Zapisz ponownie, aby potwierdzić."
|
||||
},
|
||||
"validation_header_name": "Wpisz nazwę nagłówka, która zawiera tylko litery A–Z, cyfry, łączniki i podkreślenia, na przykład X-Spam-Flag."
|
||||
},
|
||||
@@ -942,7 +959,8 @@
|
||||
"no_name": "(bez nazwy)",
|
||||
"primary": "główna",
|
||||
"save_failed": "Zapisywanie nie powiodło się",
|
||||
"text_signature": "Podpis tekstowy"
|
||||
"text_signature": "Podpis tekstowy",
|
||||
"title": "Tożsamości nadawcy"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Kolorowe etykiety do porządkowania poczty.",
|
||||
@@ -1041,6 +1059,10 @@
|
||||
"up_distributor_desc": "Zainstalowano kilka aplikacji dystrybutora UnifiedPush – wybierz tę, która ma dostarczać powiadomienia.",
|
||||
"up_distributor_label": "Dystrybutor",
|
||||
"up_no_distributor": "Na tym urządzeniu nie zainstalowano aplikacji dystrybutora UnifiedPush. Zainstaluj ją (np. ntfy) i spróbuj ponownie."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "Rozpoznawaj zaproszenia e-mail",
|
||||
"invitation_parsing_desc": "Wykrywaj zaproszenia kalendarzowe w załącznikach wiadomości e-mail i pokazuj akcje kalendarza"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1178,7 +1200,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Wtyczki",
|
||||
"updates": "Aktualizacje"
|
||||
"updates": "Aktualizacje",
|
||||
"content_senders": "Treść i nadawcy",
|
||||
"downloads": "Pobrane",
|
||||
"identities": "Tożsamości",
|
||||
"keywords": "Etykiety",
|
||||
"language": "Język, region i czas",
|
||||
"layout": "Układ",
|
||||
"sidebar_apps": "Aplikacje paska bocznego",
|
||||
"templates": "Szablony",
|
||||
"themes": "Motywy"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "Trwale usunąć „{name}”?",
|
||||
@@ -1200,6 +1231,24 @@
|
||||
"warnings": {
|
||||
"date_format": "Daty muszą mieć format „YYYY-MM-DD” lub „YYYY-MM-DD HH:MM”"
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Co minutę",
|
||||
"2m": "Co 2 minuty",
|
||||
"30s": "Co 30 sekund",
|
||||
"5m": "Co 5 minut",
|
||||
"description": "Jak często automatycznie zapisywać szkice",
|
||||
"label": "Interwał autozapisu"
|
||||
},
|
||||
"description": "Skonfiguruj ustawienia tworzenia wiadomości e-mail",
|
||||
"title": "Redagowanie"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Zaawansowane",
|
||||
"appearance": "Wygląd",
|
||||
"general": "Ogólne",
|
||||
"privacy": "Prywatność i bezpieczeństwo"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+54
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Programação",
|
||||
"three_days": "3 dias"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Excluir este evento?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Adicionar etiqueta",
|
||||
"delete_confirm_mobile": "{count, plural, one {Excluir # contato? Esta ação não pode ser desfeita.} other {Excluir # contatos? Esta ação não pode ser desfeita.}}",
|
||||
"move_to_address_book": "Mover para catálogo de endereços",
|
||||
"tag_failed": "Falha ao aplicar a etiqueta"
|
||||
"tag_failed": "Falha ao aplicar a etiqueta",
|
||||
"delete_confirm_title": "Excluir contatos"
|
||||
},
|
||||
"contact": "Contato",
|
||||
"create_hint_mobile": "Toque e segure para criar um grupo",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Toque e segure uma etiqueta para renomeá-la",
|
||||
"tag_name_placeholder": "Nome da nova etiqueta",
|
||||
"unnamed": "Sem nome"
|
||||
"unnamed": "Sem nome",
|
||||
"delete_confirm_title": "Excluir contato"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Etiqueta",
|
||||
@@ -485,7 +490,8 @@
|
||||
"no_identity": "Nenhuma identidade de envio disponível"
|
||||
},
|
||||
"view_dark_mode": "Ver no modo escuro",
|
||||
"view_light_mode": "Ver no modo claro"
|
||||
"view_light_mode": "Ver no modo claro",
|
||||
"not_spam_short": "Não spam"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "As pastas são excluídas junto com tudo o que há dentro delas. Esta ação não pode ser desfeita.",
|
||||
@@ -523,7 +529,12 @@
|
||||
"symlink": "Link simbólico",
|
||||
"upload_failed": "{count, plural, one {Não foi possível carregar 1 arquivo} other {Não foi possível carregar # arquivos}}",
|
||||
"upload_unavailable": "O seletor de arquivos não está instalado nesta versão. Recompile o app para ativar os envios.",
|
||||
"uploading_progress": "Enviando {current}/{total} · {name}"
|
||||
"uploading_progress": "Enviando {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "Tem a certeza de que deseja eliminar {count, plural, one {1 item} other {# itens}}? Esta ação não pode ser desfeita.",
|
||||
"batch_delete_success": "{count, plural, one {1 item eliminado} other {# itens eliminados}}",
|
||||
"delete_confirm_message": "Tem a certeza de que deseja eliminar \"{name}\"? Esta ação não pode ser desfeita.",
|
||||
"delete_confirm_title": "Eliminar recurso",
|
||||
"delete_success": "Eliminado com sucesso"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -892,6 +903,10 @@
|
||||
"sync_add_failed": "Não foi possível sincronizar com o catálogo de endereços",
|
||||
"sync_remove_failed": "Não foi possível remover do catálogo de endereços",
|
||||
"synced_badge": "Sincronizado pelo catálogo de endereços"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "Mostrar e-mails como mensagens individuais em vez de agrupados por conversa",
|
||||
"label": "Desativar agrupamento de conversas"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -901,7 +916,9 @@
|
||||
"remove_action": "Remover ação",
|
||||
"remove_condition": "Remover condição",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# linha} other {# linhas}}"
|
||||
"line_count": "{count, plural, one {# linha} other {# linhas}}",
|
||||
"confirm_save": "Confirmar guardar",
|
||||
"save_warning": "Guardar irá substituir todas as regras visuais. Esta ação não pode ser desfeita. Clique em Guardar novamente para confirmar."
|
||||
},
|
||||
"validation_header_name": "Insira um nome de cabeçalho que use apenas as letras A-Z, algarismos, hífens e sublinhados, como X-Spam-Flag."
|
||||
},
|
||||
@@ -945,7 +962,8 @@
|
||||
"no_name": "(sem nome)",
|
||||
"primary": "principal",
|
||||
"save_failed": "Falha ao salvar",
|
||||
"text_signature": "Assinatura em texto simples"
|
||||
"text_signature": "Assinatura em texto simples",
|
||||
"title": "Identidades de Envio"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Etiquetas coloridas para organizar seus e-mails.",
|
||||
@@ -1044,6 +1062,10 @@
|
||||
"up_distributor_desc": "Vários apps distribuidores UnifiedPush estão instalados - escolha o que deve entregar as notificações.",
|
||||
"up_distributor_label": "Distribuidor",
|
||||
"up_no_distributor": "Nenhum app distribuidor UnifiedPush está instalado neste dispositivo. Instale um (por exemplo, o ntfy) e tente novamente."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "Analisar convites por e-mail",
|
||||
"invitation_parsing_desc": "Detectar convites de calendário em anexos de e-mail e mostrar ações de calendário"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1181,7 +1203,14 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Plugins",
|
||||
"updates": "Atualizações"
|
||||
"updates": "Atualizações",
|
||||
"content_senders": "Conteúdo e remetentes",
|
||||
"identities": "Identidades",
|
||||
"keywords": "Etiquetas",
|
||||
"language": "Idioma, Região e Hora",
|
||||
"sidebar_apps": "Apps da barra lateral",
|
||||
"templates": "Modelos",
|
||||
"themes": "Temas"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "Excluir \"{name}\" permanentemente?",
|
||||
@@ -1203,6 +1232,24 @@
|
||||
"warnings": {
|
||||
"date_format": "As datas devem estar no formato \"YYYY-MM-DD\" ou \"YYYY-MM-DD HH:MM\""
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "A cada minuto",
|
||||
"2m": "A cada 2 minutos",
|
||||
"30s": "A cada 30 segundos",
|
||||
"5m": "A cada 5 minutos",
|
||||
"description": "Com que frequência salvar rascunhos automaticamente",
|
||||
"label": "Intervalo de Salvamento Automático"
|
||||
},
|
||||
"description": "Configure as configurações de composição de e-mail",
|
||||
"title": "Editor"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Avançado",
|
||||
"appearance": "Aparência",
|
||||
"general": "Geral",
|
||||
"privacy": "Privacidade e segurança"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+56
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Program",
|
||||
"three_days": "3 zile"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Doriți să ștergeți acest eveniment?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Adăugați etichetă",
|
||||
"delete_confirm_mobile": "{count, plural, one {Ștergeți # contact? Această acțiune nu poate fi anulată.} few {Ștergeți # contacte? Această acțiune nu poate fi anulată.} other {Ștergeți # de contacte? Această acțiune nu poate fi anulată.}}",
|
||||
"move_to_address_book": "Mutați în agendă",
|
||||
"tag_failed": "Etichetarea a eșuat"
|
||||
"tag_failed": "Etichetarea a eșuat",
|
||||
"delete_confirm_title": "Ștergeți contactele"
|
||||
},
|
||||
"contact": "Contact",
|
||||
"create_hint_mobile": "Apăsați lung pentru a crea un grup",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Apăsați lung o etichetă pentru a o redenumi",
|
||||
"tag_name_placeholder": "Numele noii etichete",
|
||||
"unnamed": "Fără nume"
|
||||
"unnamed": "Fără nume",
|
||||
"delete_confirm_title": "Șterge contactul"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Etichetă",
|
||||
@@ -482,7 +487,8 @@
|
||||
"no_identity": "Nu există nicio identitate de trimitere disponibilă"
|
||||
},
|
||||
"view_dark_mode": "Vizualizați în modul întunecat",
|
||||
"view_light_mode": "Vizualizați în modul luminos"
|
||||
"view_light_mode": "Vizualizați în modul luminos",
|
||||
"not_spam_short": "Nu este spam"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Folderele sunt șterse împreună cu tot conținutul lor. Această acțiune nu poate fi anulată.",
|
||||
@@ -520,7 +526,12 @@
|
||||
"symlink": "Legătură simbolică",
|
||||
"upload_failed": "{count, plural, one {1 fișier nu a putut fi încărcat} few {# fișiere nu au putut fi încărcate} other {# de fișiere nu au putut fi încărcate}}",
|
||||
"upload_unavailable": "Selectorul de fișiere nu este instalat în această versiune. Recompilați aplicația pentru a activa încărcările.",
|
||||
"uploading_progress": "Se încarcă {current}/{total} · {name}"
|
||||
"uploading_progress": "Se încarcă {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "Sunteți sigur că doriți să ștergeți {count, plural, one {1 element} few {# elemente} other {# de elemente}}? Această acțiune nu poate fi anulată.",
|
||||
"batch_delete_success": "{count, plural, one {1 element șters} few {# elemente șterse} other {# de elemente șterse}}",
|
||||
"delete_confirm_message": "Sunteți sigur că doriți să ștergeți „{name}”? Această acțiune nu poate fi anulată.",
|
||||
"delete_confirm_title": "Ștergeți resursa",
|
||||
"delete_success": "Șters cu succes"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -889,6 +900,10 @@
|
||||
"sync_add_failed": "Nu s-a putut sincroniza cu agenda",
|
||||
"sync_remove_failed": "Nu s-a putut elimina din agendă",
|
||||
"synced_badge": "Sincronizat prin agendă"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "Afișați e-mailurile ca mesaje individuale, în loc să le grupați pe conversații",
|
||||
"label": "Dezactivați gruparea conversațiilor"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -898,7 +913,9 @@
|
||||
"remove_action": "Eliminați acțiunea",
|
||||
"remove_condition": "Eliminați condiția",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# linie} few {# linii} other {# de linii}}"
|
||||
"line_count": "{count, plural, one {# linie} few {# linii} other {# de linii}}",
|
||||
"confirm_save": "Confirmare Salvare",
|
||||
"save_warning": "Salvarea va suprascrie orice reguli vizuale. Această acțiune nu poate fi anulată. Faceți clic din nou pe Salvare pentru a confirma."
|
||||
},
|
||||
"validation_header_name": "Introduceți un nume de antet care folosește doar literele A–Z, cifre, cratime și liniuțe de subliniere, de exemplu X-Spam-Flag."
|
||||
},
|
||||
@@ -942,7 +959,8 @@
|
||||
"no_name": "(fără nume)",
|
||||
"primary": "principală",
|
||||
"save_failed": "Salvarea a eșuat",
|
||||
"text_signature": "Semnătură în text simplu"
|
||||
"text_signature": "Semnătură în text simplu",
|
||||
"title": "Identități de expediere"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Etichete colorate pentru organizarea e-mailurilor.",
|
||||
@@ -1041,6 +1059,10 @@
|
||||
"up_distributor_desc": "Sunt instalate mai multe aplicații distribuitor UnifiedPush - alegeți-o pe cea care trebuie să livreze notificările.",
|
||||
"up_distributor_label": "Distribuitor",
|
||||
"up_no_distributor": "Pe acest dispozitiv nu este instalată nicio aplicație distribuitor UnifiedPush. Instalați una (de exemplu ntfy) și încercați din nou."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "Analizați invitațiile prin e-mail",
|
||||
"invitation_parsing_desc": "Detectați invitațiile din calendar din atașamentele de e-mail și afișați acțiunile din calendar"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1178,7 +1200,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Pluginuri",
|
||||
"updates": "Actualizări"
|
||||
"updates": "Actualizări",
|
||||
"content_senders": "Conținut și expeditori",
|
||||
"downloads": "Descărcări",
|
||||
"identities": "Identități",
|
||||
"keywords": "Etichete",
|
||||
"language": "Limba, regiunea și ora",
|
||||
"layout": "Aspect",
|
||||
"sidebar_apps": "Aplicații din bara laterală",
|
||||
"templates": "Șabloane",
|
||||
"themes": "Teme"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "Ștergeți definitiv „{name}”?",
|
||||
@@ -1200,6 +1231,24 @@
|
||||
"warnings": {
|
||||
"date_format": "Datele trebuie să fie în formatul \"YYYY-MM-DD\" sau \"YYYY-MM-DD HH:MM\""
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "În fiecare minut",
|
||||
"2m": "La fiecare 2 minute",
|
||||
"30s": "La fiecare 30 de secunde",
|
||||
"5m": "La fiecare 5 minute",
|
||||
"description": "Cât de des să se salveze automat schițele",
|
||||
"label": "Interval de salvare automată"
|
||||
},
|
||||
"description": "Configurați setările de redactare a e-mailurilor",
|
||||
"title": "Redactare"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Nivel avansat",
|
||||
"appearance": "Aspect",
|
||||
"general": "Generalități",
|
||||
"privacy": "Confidențialitate și securitate"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+57
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Расписание",
|
||||
"three_days": "3 дня"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Удалить это событие?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Добавить тег",
|
||||
"delete_confirm_mobile": "{count, plural, one {Удалить # контакт? Это действие нельзя отменить.} few {Удалить # контакта? Это действие нельзя отменить.} many {Удалить # контактов? Это действие нельзя отменить.} other {Удалить # контакта? Это действие нельзя отменить.}}",
|
||||
"move_to_address_book": "Переместить в адресную книгу",
|
||||
"tag_failed": "Не удалось добавить тег"
|
||||
"tag_failed": "Не удалось добавить тег",
|
||||
"delete_confirm_title": "Удалить контакты"
|
||||
},
|
||||
"contact": "Контакт",
|
||||
"create_hint_mobile": "Удерживайте, чтобы создать группу",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Удерживайте тег, чтобы переименовать его",
|
||||
"tag_name_placeholder": "Новое название тега",
|
||||
"unnamed": "Без имени"
|
||||
"unnamed": "Без имени",
|
||||
"delete_confirm_title": "Удалить контакт"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Тег",
|
||||
@@ -485,7 +490,9 @@
|
||||
"no_identity": "Нет удостоверения для отправки"
|
||||
},
|
||||
"view_dark_mode": "Показать в тёмной теме",
|
||||
"view_light_mode": "Показать в светлой теме"
|
||||
"view_light_mode": "Показать в светлой теме",
|
||||
"not_spam_short": "Не спам",
|
||||
"spam_short": "Спам"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Папки удаляются вместе со всем содержимым. Это действие нельзя отменить.",
|
||||
@@ -523,7 +530,12 @@
|
||||
"symlink": "Символическая ссылка",
|
||||
"upload_failed": "{count, plural, one {Не удалось загрузить # файл} few {Не удалось загрузить # файла} many {Не удалось загрузить # файлов} other {Не удалось загрузить # файла}}",
|
||||
"upload_unavailable": "В этой сборке нет модуля выбора файлов. Пересоберите приложение, чтобы включить загрузку.",
|
||||
"uploading_progress": "Загрузка {current}/{total} · {name}"
|
||||
"uploading_progress": "Загрузка {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "Вы уверены, что хотите удалить {count, plural, one {1 элемент} other {# элементов}}? Это действие нельзя отменить.",
|
||||
"batch_delete_success": "{count, plural, one {1 элемент удалён} other {# элементов удалено}}",
|
||||
"delete_confirm_message": "Вы уверены, что хотите удалить «{name}»? Это действие нельзя отменить.",
|
||||
"delete_confirm_title": "Удалить ресурс",
|
||||
"delete_success": "Успешно удалено"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -892,6 +904,10 @@
|
||||
"sync_add_failed": "Не удалось синхронизировать с адресной книгой",
|
||||
"sync_remove_failed": "Не удалось удалить из адресной книги",
|
||||
"synced_badge": "Синхронизировано через адресную книгу"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "Отображать письма как отдельные сообщения, а не сгруппированные по беседам",
|
||||
"label": "Отключить группировку по беседам"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -901,7 +917,9 @@
|
||||
"remove_action": "Удалить действие",
|
||||
"remove_condition": "Удалить условие",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# строка} few {# строки} many {# строк} other {# строки}}"
|
||||
"line_count": "{count, plural, one {# строка} few {# строки} many {# строк} other {# строки}}",
|
||||
"confirm_save": "Подтвердить сохранение",
|
||||
"save_warning": "Сохранение перезапишет визуальные правила. Это нельзя отменить. Нажмите «Сохранить» ещё раз для подтверждения."
|
||||
},
|
||||
"validation_header_name": "Введите имя заголовка, состоящее только из латинских букв A–Z, цифр, дефисов и подчёркиваний, например X-Spam-Flag."
|
||||
},
|
||||
@@ -945,7 +963,8 @@
|
||||
"no_name": "(без имени)",
|
||||
"primary": "основное",
|
||||
"save_failed": "Не удалось сохранить",
|
||||
"text_signature": "Подпись в виде простого текста"
|
||||
"text_signature": "Подпись в виде простого текста",
|
||||
"title": "Удостоверения отправителя"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Цветные теги для упорядочивания почты.",
|
||||
@@ -1044,6 +1063,10 @@
|
||||
"up_distributor_desc": "Установлено несколько приложений-дистрибьюторов UnifiedPush — выберите, какое будет доставлять уведомления.",
|
||||
"up_distributor_label": "Дистрибьютор",
|
||||
"up_no_distributor": "На этом устройстве нет приложения-дистрибьютора UnifiedPush. Установите его (например, ntfy) и повторите попытку."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "Распознавать приглашения по почте",
|
||||
"invitation_parsing_desc": "Обнаруживать приглашения календаря во вложениях писем и показывать действия календаря"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1181,7 +1204,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Плагины",
|
||||
"updates": "Обновления"
|
||||
"updates": "Обновления",
|
||||
"content_senders": "Содержимое и отправители",
|
||||
"downloads": "Загрузки",
|
||||
"identities": "Удостоверения",
|
||||
"keywords": "Теги",
|
||||
"language": "Язык, регион и время",
|
||||
"layout": "Макет",
|
||||
"sidebar_apps": "Приложения боковой панели",
|
||||
"templates": "Шаблоны",
|
||||
"themes": "Темы"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "Удалить «{name}» навсегда?",
|
||||
@@ -1203,6 +1235,24 @@
|
||||
"warnings": {
|
||||
"date_format": "Даты должны быть в формате \"YYYY-MM-DD\" или \"YYYY-MM-DD HH:MM\""
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Каждую минуту",
|
||||
"2m": "Каждые 2 минуты",
|
||||
"30s": "Каждые 30 секунд",
|
||||
"5m": "Каждые 5 минут",
|
||||
"description": "Как часто автоматически сохранять черновики",
|
||||
"label": "Интервал автосохранения"
|
||||
},
|
||||
"description": "Настройте параметры написания писем",
|
||||
"title": "Редактор"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Дополнительно",
|
||||
"appearance": "Внешний вид",
|
||||
"general": "Общие",
|
||||
"privacy": "Конфиденциальность и безопасность"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+56
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Program",
|
||||
"three_days": "3 dni"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Odstrániť túto udalosť?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Pridať štítok",
|
||||
"delete_confirm_mobile": "{count, plural, one {Odstrániť # kontakt? Túto akciu nie je možné vrátiť späť.} few {Odstrániť # kontakty? Túto akciu nie je možné vrátiť späť.} many {Odstrániť # kontaktu? Túto akciu nie je možné vrátiť späť.} other {Odstrániť # kontaktov? Túto akciu nie je možné vrátiť späť.}}",
|
||||
"move_to_address_book": "Presunúť do adresára",
|
||||
"tag_failed": "Štítok sa nepodarilo pridať"
|
||||
"tag_failed": "Štítok sa nepodarilo pridať",
|
||||
"delete_confirm_title": "Odstrániť kontakty"
|
||||
},
|
||||
"contact": "Kontakt",
|
||||
"create_hint_mobile": "Dlhým stlačením vytvoríte skupinu",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Štítok premenujete dlhým stlačením",
|
||||
"tag_name_placeholder": "Názov nového štítka",
|
||||
"unnamed": "Bez mena"
|
||||
"unnamed": "Bez mena",
|
||||
"delete_confirm_title": "Odstrániť kontakt"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Štítok",
|
||||
@@ -481,7 +486,8 @@
|
||||
"no_identity": "Nie je k dispozícii žiadna identita na odosielanie"
|
||||
},
|
||||
"view_dark_mode": "Zobraziť v tmavom režime",
|
||||
"view_light_mode": "Zobraziť vo svetlom režime"
|
||||
"view_light_mode": "Zobraziť vo svetlom režime",
|
||||
"not_spam_short": "Nie je spam"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Priečinky sa odstránia spolu so všetkým obsahom. Túto akciu nie je možné vrátiť späť.",
|
||||
@@ -519,7 +525,12 @@
|
||||
"symlink": "Symbolický odkaz",
|
||||
"upload_failed": "{count, plural, one {1 súbor sa nepodarilo nahrať} few {# súbory sa nepodarilo nahrať} other {# súborov sa nepodarilo nahrať}}",
|
||||
"upload_unavailable": "Výber súborov nie je v tejto zostave nainštalovaný. Ak chcete povoliť nahrávanie, zostavte aplikáciu znova.",
|
||||
"uploading_progress": "Nahrávanie {current}/{total} · {name}"
|
||||
"uploading_progress": "Nahrávanie {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "Naozaj chcete odstrániť {count, plural, one {1 položku} other {# položiek}}? Túto akciu nie je možné vrátiť.",
|
||||
"batch_delete_success": "{count, plural, one {Odstránená 1 položka} other {Odstránených # položiek}}",
|
||||
"delete_confirm_message": "Naozaj chcete odstrániť \"{name}\"? Túto akciu nie je možné vrátiť.",
|
||||
"delete_confirm_title": "Odstrániť položku",
|
||||
"delete_success": "Úspešne odstránené"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -888,6 +899,10 @@
|
||||
"sync_add_failed": "Synchronizácia do adresára zlyhala",
|
||||
"sync_remove_failed": "Odstránenie z adresára zlyhalo",
|
||||
"synced_badge": "Synchronizované cez adresár"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "Zobrazovať e-maily ako jednotlivé správy namiesto zoskupovania do vlákien",
|
||||
"label": "Zakázať zoskupovanie konverzácií"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -897,7 +912,9 @@
|
||||
"remove_action": "Odstrániť akciu",
|
||||
"remove_condition": "Odstrániť podmienku",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# riadok} few {# riadky} many {# riadka} other {# riadkov}}"
|
||||
"line_count": "{count, plural, one {# riadok} few {# riadky} many {# riadka} other {# riadkov}}",
|
||||
"confirm_save": "Potvrdiť uloženie",
|
||||
"save_warning": "Uloženie prepíše všetky vizuálne pravidlá. Túto akciu nie je možné vrátiť. Potvrďte opätovným kliknutím na Uložiť."
|
||||
},
|
||||
"validation_header_name": "Zadajte názov hlavičky, ktorý obsahuje iba písmená A – Z, číslice, spojovníky a podčiarkovníky, napríklad X-Spam-Flag."
|
||||
},
|
||||
@@ -941,7 +958,8 @@
|
||||
"no_name": "(bez mena)",
|
||||
"primary": "hlavná",
|
||||
"save_failed": "Uloženie zlyhalo",
|
||||
"text_signature": "Textový podpis"
|
||||
"text_signature": "Textový podpis",
|
||||
"title": "Identity odosielateľa"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Farebné štítky na organizáciu pošty.",
|
||||
@@ -1040,6 +1058,10 @@
|
||||
"up_distributor_desc": "Je nainštalovaných niekoľko aplikácií distribútora UnifiedPush – vyberte tú, ktorá má doručovať oznámenia.",
|
||||
"up_distributor_label": "Distribútor",
|
||||
"up_no_distributor": "V tomto zariadení nie je nainštalovaná žiadna aplikácia distribútora UnifiedPush. Nainštalujte si ju (napríklad ntfy) a skúste to znova."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "Rozpoznávať e-mailové pozvánky",
|
||||
"invitation_parsing_desc": "Rozpoznávať pozvánky kalendára v prílohách e-mailov a zobrazovať akcie kalendára"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1177,7 +1199,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Pluginy",
|
||||
"updates": "Aktualizácie"
|
||||
"updates": "Aktualizácie",
|
||||
"content_senders": "Obsah a odosielatelia",
|
||||
"downloads": "Stiahnuté",
|
||||
"identities": "Identity",
|
||||
"keywords": "Štítky",
|
||||
"language": "Jazyk, región a čas",
|
||||
"layout": "Rozloženie",
|
||||
"sidebar_apps": "Aplikácie postranného panela",
|
||||
"templates": "Šablóny",
|
||||
"themes": "Motívy"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "Natrvalo odstrániť „{name}“?",
|
||||
@@ -1199,6 +1230,24 @@
|
||||
"warnings": {
|
||||
"date_format": "Dátumy musia byť vo formáte „YYYY-MM-DD“ alebo „YYYY-MM-DD HH:MM“"
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Každú minútu",
|
||||
"2m": "Každé 2 minúty",
|
||||
"30s": "Každých 30 sekúnd",
|
||||
"5m": "Každých 5 minút",
|
||||
"description": "Ako často sa majú koncepty automaticky ukladať",
|
||||
"label": "Interval automatického ukladania"
|
||||
},
|
||||
"description": "Nakonfigurujte nastavenia pre písanie e-mailov",
|
||||
"title": "Skladanie správ"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Pokročilé",
|
||||
"appearance": "Vzhľad",
|
||||
"general": "Všeobecné",
|
||||
"privacy": "Súkromie a zabezpečenie"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
+56
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Program",
|
||||
"three_days": "3 gün"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Bu etkinlik silinsin mi?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Etiket ekle",
|
||||
"delete_confirm_mobile": "{count, plural, one {# kişi silinsin mi? Bu geri alınamaz.} other {# kişi silinsin mi? Bu geri alınamaz.}}",
|
||||
"move_to_address_book": "Adres defterine taşı",
|
||||
"tag_failed": "Etiketleme başarısız oldu"
|
||||
"tag_failed": "Etiketleme başarısız oldu",
|
||||
"delete_confirm_title": "Kişileri sil"
|
||||
},
|
||||
"contact": "Kişi",
|
||||
"create_hint_mobile": "Bunun yerine grup oluşturmak için uzun basın",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Yeniden adlandırmak için bir etikete uzun basın",
|
||||
"tag_name_placeholder": "Yeni etiket adı",
|
||||
"unnamed": "Adsız"
|
||||
"unnamed": "Adsız",
|
||||
"delete_confirm_title": "Kişiyi sil"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Etiket",
|
||||
@@ -483,7 +488,8 @@
|
||||
"no_identity": "Kullanılabilir gönderim kimliği yok"
|
||||
},
|
||||
"view_dark_mode": "Koyu modda görüntüle",
|
||||
"view_light_mode": "Açık modda görüntüle"
|
||||
"view_light_mode": "Açık modda görüntüle",
|
||||
"not_spam_short": "Spam değil"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Klasörler, içindeki her şeyle birlikte silinir. Bu işlem geri alınamaz.",
|
||||
@@ -521,7 +527,12 @@
|
||||
"symlink": "Sembolik bağlantı",
|
||||
"upload_failed": "{count, plural, one {1 dosya yüklenemedi} other {# dosya yüklenemedi}}",
|
||||
"upload_unavailable": "Bu derlemede dosya seçici yüklü değil. Yüklemeleri etkinleştirmek için uygulamayı yeniden derleyin.",
|
||||
"uploading_progress": "Yükleniyor {current}/{total} · {name}"
|
||||
"uploading_progress": "Yükleniyor {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "{count, plural, one {1 öge} other {# öge}} silinsin mi? Bu geri alınamaz.",
|
||||
"batch_delete_success": "{count, plural, one {1 öge silindi} other {# öge silindi}}",
|
||||
"delete_confirm_message": "\"{name}\" silinsin mi? Bu geri alınamaz.",
|
||||
"delete_confirm_title": "Kaynağı sil",
|
||||
"delete_success": "Başarıyla silindi"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -890,6 +901,10 @@
|
||||
"sync_add_failed": "Adres defteriyle senkronize edilemedi",
|
||||
"sync_remove_failed": "Adres defterinden kaldırılamadı",
|
||||
"synced_badge": "Adres defteri aracılığıyla senkronize edildi"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "E-postaları konuşmaya göre gruplamak yerine ayrı iletiler olarak göster",
|
||||
"label": "Konuşma Gruplamamayı Devre Dışı Bırak"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -899,7 +914,9 @@
|
||||
"remove_action": "İşlemi kaldır",
|
||||
"remove_condition": "Koşulu kaldır",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# satır} other {# satır}}"
|
||||
"line_count": "{count, plural, one {# satır} other {# satır}}",
|
||||
"confirm_save": "Kaydetmeyi Onayla",
|
||||
"save_warning": "Kaydetme işlemi tüm görsel kuralların üzerine yazacak. Bu geri alınamaz. Onaylamak için Kaydet'e tekrar tıklayın."
|
||||
},
|
||||
"validation_header_name": "Yalnızca A-Z harfleri, rakamlar, kısa çizgiler ve alt çizgiler içeren bir başlık adı girin; örneğin X-Spam-Flag."
|
||||
},
|
||||
@@ -943,7 +960,8 @@
|
||||
"no_name": "(ad yok)",
|
||||
"primary": "birincil",
|
||||
"save_failed": "Kaydetme başarısız",
|
||||
"text_signature": "Düz metin imzası"
|
||||
"text_signature": "Düz metin imzası",
|
||||
"title": "Gönderme Kimlikleri"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Postanızı düzenlemek için renkli etiketler.",
|
||||
@@ -1042,6 +1060,10 @@
|
||||
"up_distributor_desc": "Birden fazla UnifiedPush dağıtıcı uygulaması yüklü - bildirimleri hangisinin ileteceğini seçin.",
|
||||
"up_distributor_label": "Dağıtıcı",
|
||||
"up_no_distributor": "Bu cihazda UnifiedPush dağıtıcı uygulaması yüklü değil. Bir tane yükleyin (ör. ntfy) ve tekrar deneyin."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "E-posta davetlerini ayrıştır",
|
||||
"invitation_parsing_desc": "E-posta eklerindeki takvim davetlerini algıla ve takvim işlemlerini göster"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1179,7 +1201,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Eklentiler",
|
||||
"updates": "Güncellemeler"
|
||||
"updates": "Güncellemeler",
|
||||
"content_senders": "İçerik ve Göndericiler",
|
||||
"downloads": "İndirilenler",
|
||||
"identities": "Kimlikler",
|
||||
"keywords": "Etiketler",
|
||||
"language": "Dil, Bölge ve Saat",
|
||||
"layout": "Düzen",
|
||||
"sidebar_apps": "Kenar Çubuğu Uygulamaları",
|
||||
"templates": "Şablonlar",
|
||||
"themes": "Temalar"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "\"{name}\" kalıcı olarak silinsin mi?",
|
||||
@@ -1201,6 +1232,24 @@
|
||||
"warnings": {
|
||||
"date_format": "Tarihler \"YYYY-MM-DD\" veya \"YYYY-MM-DD HH:MM\" biçiminde olmalıdır"
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Her dakika",
|
||||
"2m": "Her 2 dakikada bir",
|
||||
"30s": "Her 30 saniyede bir",
|
||||
"5m": "Her 5 dakikada bir",
|
||||
"description": "Taslakların otomatik kaydedilme sıklığı",
|
||||
"label": "Otomatik Kaydetme Aralığı"
|
||||
},
|
||||
"description": "E-posta yazma ayarlarını yapılandırın",
|
||||
"title": "İleti düzenleyici"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Gelişmiş",
|
||||
"appearance": "Görünüm",
|
||||
"general": "Genel",
|
||||
"privacy": "Gizlilik ve Güvenlik"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+57
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "Розклад",
|
||||
"three_days": "3 дні"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "Видалити цю подію?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "Додати тег",
|
||||
"delete_confirm_mobile": "{count, plural, one {Видалити # контакт? Цю дію не можна скасувати.} few {Видалити # контакти? Цю дію не можна скасувати.} many {Видалити # контактів? Цю дію не можна скасувати.} other {Видалити # контакту? Цю дію не можна скасувати.}}",
|
||||
"move_to_address_book": "Перемістити до адресної книги",
|
||||
"tag_failed": "Не вдалося додати тег"
|
||||
"tag_failed": "Не вдалося додати тег",
|
||||
"delete_confirm_title": "Видалити контакти"
|
||||
},
|
||||
"contact": "Контакт",
|
||||
"create_hint_mobile": "Утримуйте, щоб створити групу",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "Утримуйте тег, щоб перейменувати його",
|
||||
"tag_name_placeholder": "Нова назва тегу",
|
||||
"unnamed": "Без імені"
|
||||
"unnamed": "Без імені",
|
||||
"delete_confirm_title": "Видалити контакт"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Тег",
|
||||
@@ -484,7 +489,9 @@
|
||||
"no_identity": "Немає ідентичності для надсилання"
|
||||
},
|
||||
"view_dark_mode": "Переглянути в темній темі",
|
||||
"view_light_mode": "Переглянути у світлій темі"
|
||||
"view_light_mode": "Переглянути у світлій темі",
|
||||
"not_spam_short": "Не спам",
|
||||
"spam_short": "Спам"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Папки видаляються разом з усім вмістом. Цю дію не можна скасувати.",
|
||||
@@ -522,7 +529,12 @@
|
||||
"symlink": "Символічне посилання",
|
||||
"upload_failed": "{count, plural, one {Не вдалося завантажити # файл} few {Не вдалося завантажити # файли} many {Не вдалося завантажити # файлів} other {Не вдалося завантажити # файлу}}",
|
||||
"upload_unavailable": "У цій збірці немає модуля вибору файлів. Перезберіть застосунок, щоб увімкнути завантаження файлів.",
|
||||
"uploading_progress": "Завантаження {current}/{total} · {name}"
|
||||
"uploading_progress": "Завантаження {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "Ви впевнені, що хочете видалити {count, plural, one {1 item} other {# items}}? Це неможливо скасувати.",
|
||||
"batch_delete_success": "{count, plural, one {1 елемент видалено} few {# елементи видалено} many {# елементів видалено} other {# елементів видалено}}",
|
||||
"delete_confirm_message": "Ви впевнені, що хочете видалити \"{name}\"? Це неможливо скасувати.",
|
||||
"delete_confirm_title": "Видалити ресурс",
|
||||
"delete_success": "Успішно видалено"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -891,6 +903,10 @@
|
||||
"sync_add_failed": "Не вдалося синхронізувати з адресною книгою",
|
||||
"sync_remove_failed": "Не вдалося видалити з адресної книги",
|
||||
"synced_badge": "Синхронізовано через адресну книгу"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "Показувати електронні листи як окремі повідомлення, а не згруповані за розмовами",
|
||||
"label": "Вимкнути групування розмов"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -900,7 +916,9 @@
|
||||
"remove_action": "Видалити дію",
|
||||
"remove_condition": "Видалити умову",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# рядок} few {# рядки} many {# рядків} other {# рядка}}"
|
||||
"line_count": "{count, plural, one {# рядок} few {# рядки} many {# рядків} other {# рядка}}",
|
||||
"confirm_save": "Підтвердити збереження",
|
||||
"save_warning": "Збереження перезапише будь-які візуальні правила. Це неможливо скасувати. Натисніть кнопку Зберегти ще раз для підтвердження."
|
||||
},
|
||||
"validation_header_name": "Введіть назву заголовка, що містить лише латинські літери A–Z, цифри, дефіси та підкреслення, наприклад X-Spam-Flag."
|
||||
},
|
||||
@@ -944,7 +962,8 @@
|
||||
"no_name": "(без імені)",
|
||||
"primary": "основна",
|
||||
"save_failed": "Не вдалося зберегти",
|
||||
"text_signature": "Підпис простим текстом"
|
||||
"text_signature": "Підпис простим текстом",
|
||||
"title": "Надсилання ідентифікаторів"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "Кольорові теги для впорядкування пошти.",
|
||||
@@ -1043,6 +1062,10 @@
|
||||
"up_distributor_desc": "Установлено кілька застосунків-дистриб'юторів UnifiedPush — виберіть, який доставлятиме сповіщення.",
|
||||
"up_distributor_label": "Дистриб'ютор",
|
||||
"up_no_distributor": "На цьому пристрої немає застосунку-дистриб'ютора UnifiedPush. Установіть його (наприклад, ntfy) і спробуйте ще раз."
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "Проаналізуйте запрошення електронною поштою",
|
||||
"invitation_parsing_desc": "Виявляти запрошення календаря у вкладеннях електронної пошти та показувати дії календаря"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1180,7 +1203,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Плагіни",
|
||||
"updates": "Оновлення"
|
||||
"updates": "Оновлення",
|
||||
"content_senders": "Вміст і відправники",
|
||||
"downloads": "Завантаження",
|
||||
"identities": "Ідентичності",
|
||||
"keywords": "Ключові слова",
|
||||
"language": "Мова, регіон і час",
|
||||
"layout": "Макет",
|
||||
"sidebar_apps": "Програми бічної панелі",
|
||||
"templates": "Шаблони",
|
||||
"themes": "Теми"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "Видалити «{name}» назавжди?",
|
||||
@@ -1202,6 +1234,24 @@
|
||||
"warnings": {
|
||||
"date_format": "Дати мають бути у форматі \"YYYY-MM-DD\" або \"YYYY-MM-DD HH:MM\""
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "Кожну хвилину",
|
||||
"2m": "Кожні 2 хвилини",
|
||||
"30s": "Кожні 30 секунд",
|
||||
"5m": "Кожні 5 хвилин",
|
||||
"description": "Як часто автоматично зберігати чернетки",
|
||||
"label": "Інтервал автозбереження"
|
||||
},
|
||||
"description": "Налаштування параметрів композиції електронної пошти",
|
||||
"title": "Редактор"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "Додатково",
|
||||
"appearance": "Вигляд",
|
||||
"general": "Загальний",
|
||||
"privacy": "Конфіденційність і безпека"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+57
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "行程",
|
||||
"three_days": "3 天"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "刪除這個活動嗎?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "新增標籤",
|
||||
"delete_confirm_mobile": "{count, plural, other {要刪除 # 位聯絡人嗎?此動作無法復原。}}",
|
||||
"move_to_address_book": "移至通訊錄",
|
||||
"tag_failed": "新增標籤失敗"
|
||||
"tag_failed": "新增標籤失敗",
|
||||
"delete_confirm_title": "刪除聯絡人"
|
||||
},
|
||||
"contact": "聯絡人",
|
||||
"create_hint_mobile": "長按即可改為建立群組",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "長按標籤即可重新命名",
|
||||
"tag_name_placeholder": "新標籤名稱",
|
||||
"unnamed": "未命名"
|
||||
"unnamed": "未命名",
|
||||
"delete_confirm_title": "刪除聯絡人"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "標籤",
|
||||
@@ -481,7 +486,9 @@
|
||||
"no_identity": "沒有可用的寄件身分"
|
||||
},
|
||||
"view_dark_mode": "以深色模式檢視",
|
||||
"view_light_mode": "以淺色模式檢視"
|
||||
"view_light_mode": "以淺色模式檢視",
|
||||
"not_spam_short": "非垃圾郵件",
|
||||
"spam_short": "垃圾郵件"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "資料夾會連同其中的所有內容一併刪除。此動作無法復原。",
|
||||
@@ -519,7 +526,12 @@
|
||||
"symlink": "符號連結",
|
||||
"upload_failed": "{count, plural, one {1 個檔案無法上傳} other {# 個檔案無法上傳}}",
|
||||
"upload_unavailable": "此版本未安裝檔案選擇器。請重新建置應用程式以啟用上傳功能。",
|
||||
"uploading_progress": "正在上傳 {current}/{total} · {name}"
|
||||
"uploading_progress": "正在上傳 {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "您確定要刪除{count, plural, one {1 個項目} other {# 個項目}}嗎?此動作無法復原。",
|
||||
"batch_delete_success": "{count, plural, one {已刪除 1 個項目} other {已刪除 # 個項目}}",
|
||||
"delete_confirm_message": "您確定要刪除「{name}」嗎?此動作無法復原。",
|
||||
"delete_confirm_title": "刪除項目",
|
||||
"delete_success": "刪除成功"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -888,6 +900,10 @@
|
||||
"sync_add_failed": "無法同步至通訊錄",
|
||||
"sync_remove_failed": "無法從通訊錄移除",
|
||||
"synced_badge": "已透過通訊錄同步"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "將郵件個別顯示,而不是依對話串分組",
|
||||
"label": "停用對話串群組"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -897,7 +913,9 @@
|
||||
"remove_action": "移除動作",
|
||||
"remove_condition": "移除條件",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, other {# 行}}"
|
||||
"line_count": "{count, plural, other {# 行}}",
|
||||
"confirm_save": "確認儲存",
|
||||
"save_warning": "儲存會覆寫所有視覺化規則,且無法復原。請再次點擊「儲存」以確認。"
|
||||
},
|
||||
"validation_header_name": "請輸入僅包含字母 A-Z、數字、連字號和底線的標頭名稱,例如 X-Spam-Flag。"
|
||||
},
|
||||
@@ -941,7 +959,8 @@
|
||||
"no_name": "(無名稱)",
|
||||
"primary": "主要",
|
||||
"save_failed": "儲存失敗",
|
||||
"text_signature": "純文字簽名檔"
|
||||
"text_signature": "純文字簽名檔",
|
||||
"title": "寄件身分"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "用來整理郵件的彩色標籤。",
|
||||
@@ -1040,6 +1059,10 @@
|
||||
"up_distributor_desc": "已安裝多個 UnifiedPush 分發器應用程式,請選擇要用來傳送通知的應用程式。",
|
||||
"up_distributor_label": "分發器",
|
||||
"up_no_distributor": "此裝置上未安裝 UnifiedPush 分發器應用程式。請安裝一個(例如 ntfy)後再試一次。"
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "解析郵件邀請",
|
||||
"invitation_parsing_desc": "偵測郵件附件中的行事曆邀請,並顯示行事曆動作"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1177,7 +1200,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "外掛程式",
|
||||
"updates": "更新"
|
||||
"updates": "更新",
|
||||
"content_senders": "內容與寄件者",
|
||||
"downloads": "下載",
|
||||
"identities": "寄件身分",
|
||||
"keywords": "標籤",
|
||||
"language": "語言、地區與時間",
|
||||
"layout": "版面配置",
|
||||
"sidebar_apps": "側邊欄應用程式",
|
||||
"templates": "範本",
|
||||
"themes": "主題"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "要永久刪除「{name}」嗎?",
|
||||
@@ -1199,6 +1231,24 @@
|
||||
"warnings": {
|
||||
"date_format": "日期格式必須為「YYYY-MM-DD」或「YYYY-MM-DD HH:MM」"
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "每分鐘",
|
||||
"2m": "每 2 分鐘",
|
||||
"30s": "每 30 秒",
|
||||
"5m": "每 5 分鐘",
|
||||
"description": "自動儲存草稿的頻率",
|
||||
"label": "自動儲存間隔"
|
||||
},
|
||||
"description": "設定郵件撰寫偏好",
|
||||
"title": "郵件撰寫"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "進階",
|
||||
"appearance": "外觀",
|
||||
"general": "一般",
|
||||
"privacy": "隱私權與安全"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+57
-7
@@ -141,6 +141,9 @@
|
||||
"views": {
|
||||
"schedule": "日程",
|
||||
"three_days": "3 天"
|
||||
},
|
||||
"detail": {
|
||||
"delete_confirm": "删除这个活动吗?"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -167,7 +170,8 @@
|
||||
"add_tag": "添加标签",
|
||||
"delete_confirm_mobile": "{count, plural, other {删除 # 位联系人?此操作无法撤销。}}",
|
||||
"move_to_address_book": "移至通讯录",
|
||||
"tag_failed": "添加标签失败"
|
||||
"tag_failed": "添加标签失败",
|
||||
"delete_confirm_title": "删除联系人"
|
||||
},
|
||||
"contact": "联系人",
|
||||
"create_hint_mobile": "长按可改为创建群组",
|
||||
@@ -260,7 +264,8 @@
|
||||
},
|
||||
"rename_tag_hint_mobile": "长按标签可重命名",
|
||||
"tag_name_placeholder": "新标签名称",
|
||||
"unnamed": "未命名"
|
||||
"unnamed": "未命名",
|
||||
"delete_confirm_title": "删除联系人"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "标签",
|
||||
@@ -481,7 +486,9 @@
|
||||
"no_identity": "没有可用的发送身份"
|
||||
},
|
||||
"view_dark_mode": "以深色模式查看",
|
||||
"view_light_mode": "以浅色模式查看"
|
||||
"view_light_mode": "以浅色模式查看",
|
||||
"not_spam_short": "非垃圾邮件",
|
||||
"spam_short": "垃圾邮件"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "文件夹将连同其中的所有内容一起删除。此操作无法撤销。",
|
||||
@@ -519,7 +526,12 @@
|
||||
"symlink": "符号链接",
|
||||
"upload_failed": "{count, plural, one {1 个文件未能上传} other {# 个文件未能上传}}",
|
||||
"upload_unavailable": "此版本未安装文件选择器。请重新构建应用以启用上传。",
|
||||
"uploading_progress": "正在上传 {current}/{total} · {name}"
|
||||
"uploading_progress": "正在上传 {current}/{total} · {name}",
|
||||
"batch_delete_confirm_message": "确定要删除{count, plural, one {1 个项目} other {# 个项目}}吗?此操作无法撤销。",
|
||||
"batch_delete_success": "{count, plural, one {已删除 1 个项目} other {已删除 # 个项目}}",
|
||||
"delete_confirm_message": "您确定要删除\"{name}\"吗?此操作无法撤消。",
|
||||
"delete_confirm_title": "删除资源",
|
||||
"delete_success": "删除成功"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -888,6 +900,10 @@
|
||||
"sync_add_failed": "无法同步到通讯录",
|
||||
"sync_remove_failed": "无法从通讯录中移除",
|
||||
"synced_badge": "已通过通讯录同步"
|
||||
},
|
||||
"disable_threading": {
|
||||
"description": "将邮件显示为独立消息,而不是按会话分组",
|
||||
"label": "禁用对话分组"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
@@ -897,7 +913,9 @@
|
||||
"remove_action": "移除操作",
|
||||
"remove_condition": "移除条件",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, other {# 行}}"
|
||||
"line_count": "{count, plural, other {# 行}}",
|
||||
"confirm_save": "确认保存",
|
||||
"save_warning": "保存将覆盖所有可视化规则,此操作无法撤销。再次点击\"保存\"以确认。"
|
||||
},
|
||||
"validation_header_name": "请输入仅包含字母 A-Z、数字、连字符和下划线的标头名称,例如 X-Spam-Flag。"
|
||||
},
|
||||
@@ -941,7 +959,8 @@
|
||||
"no_name": "(无名称)",
|
||||
"primary": "主要",
|
||||
"save_failed": "保存失败",
|
||||
"text_signature": "纯文本签名"
|
||||
"text_signature": "纯文本签名",
|
||||
"title": "发送身份"
|
||||
},
|
||||
"keywords": {
|
||||
"description_mobile": "用于整理邮件的彩色标签。",
|
||||
@@ -1040,6 +1059,10 @@
|
||||
"up_distributor_desc": "已安装多个 UnifiedPush 分发器应用,请选择用于传送通知的应用。",
|
||||
"up_distributor_label": "分发器",
|
||||
"up_no_distributor": "此设备上未安装 UnifiedPush 分发器应用。请安装一个(例如 ntfy)后重试。"
|
||||
},
|
||||
"calendar": {
|
||||
"invitation_parsing": "解析邮件邀请",
|
||||
"invitation_parsing_desc": "检测邮件附件中的日历邀请并显示日历操作"
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -1177,7 +1200,16 @@
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "插件",
|
||||
"updates": "更新"
|
||||
"updates": "更新",
|
||||
"content_senders": "内容和发件人",
|
||||
"downloads": "下载",
|
||||
"identities": "身份",
|
||||
"keywords": "标签",
|
||||
"language": "语言、地区与时间",
|
||||
"layout": "布局",
|
||||
"sidebar_apps": "侧边栏应用",
|
||||
"templates": "模板",
|
||||
"themes": "主题"
|
||||
},
|
||||
"templates": {
|
||||
"delete_confirm": "永久删除“{name}”?",
|
||||
@@ -1199,6 +1231,24 @@
|
||||
"warnings": {
|
||||
"date_format": "日期格式必须为“YYYY-MM-DD”或“YYYY-MM-DD HH:MM”"
|
||||
}
|
||||
},
|
||||
"composer": {
|
||||
"autosave": {
|
||||
"1m": "每分钟",
|
||||
"2m": "每 2 分钟",
|
||||
"30s": "每 30 秒",
|
||||
"5m": "每 5 分钟",
|
||||
"description": "自动保存草稿的频率",
|
||||
"label": "自动保存间隔"
|
||||
},
|
||||
"description": "配置邮件撰写相关设置",
|
||||
"title": "邮件撰写"
|
||||
},
|
||||
"tab_groups": {
|
||||
"advanced": "高级",
|
||||
"appearance": "外观",
|
||||
"general": "通用",
|
||||
"privacy": "隐私和安全"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
|
||||
+1749
-226
File diff suppressed because it is too large
Load Diff
+1747
-224
File diff suppressed because it is too large
Load Diff
+1743
-220
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1759
-236
File diff suppressed because it is too large
Load Diff
+1753
-230
File diff suppressed because it is too large
Load Diff
+1734
-211
File diff suppressed because it is too large
Load Diff
+1740
-217
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@ const SOURCE = process.argv[2]
|
||||
// App name -> Tabler outline name. Where the webmail (components/icons) maps
|
||||
// the same name, this uses its choice.
|
||||
const MAP = {
|
||||
Accessibility: 'accessible',
|
||||
AlarmClock: 'alarm',
|
||||
AlertCircle: 'alert-circle',
|
||||
AlertTriangle: 'alert-triangle',
|
||||
|
||||
@@ -53,10 +53,19 @@ import {
|
||||
DEFAULT_CLIENT_ID,
|
||||
MOBILE_CLIENT_ID,
|
||||
REGISTERED_REDIRECT_URI,
|
||||
STALWART_LOGIN_SCOPE,
|
||||
answerConsent,
|
||||
exchangePasswordForTokens,
|
||||
generateCodeChallenge,
|
||||
generateCodeVerifier,
|
||||
readStalwartLoginResponse,
|
||||
resolveLoginClient,
|
||||
resetLoginClients,
|
||||
stalwartLoginBody,
|
||||
TotpLoginError,
|
||||
} from '../../lib/totp-login';
|
||||
import { describeLoginError } from '../../lib/login-errors';
|
||||
import { fetchMailQuota, serverSupportsQuota, toMailQuota } from '../quota';
|
||||
import { discoverOAuthMetadata, loginWithPkce, pkceScopes, revokeRefreshToken } from '../../lib/oauth-native';
|
||||
import {
|
||||
HANDOFF_REDIRECT_URI,
|
||||
@@ -277,6 +286,132 @@ live('live Stalwart sign-in', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('1.0.1: a consentRequired answer is met with authConsent and signs in', async () => {
|
||||
if (!isV1) return;
|
||||
// A client of its own (1.0 ids are sealed, so registering stores
|
||||
// nothing) has no grant yet, so leaving `consent` out gets the question.
|
||||
const reg = await fetch(`${SERVER}/auth/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
client_name: `Bulwark Mobile consent check ${Date.now()}`,
|
||||
redirect_uris: [REGISTERED_REDIRECT_URI],
|
||||
token_endpoint_auth_method: 'none',
|
||||
grant_types: ['authorization_code', 'refresh_token'],
|
||||
response_types: ['code'],
|
||||
scope: STALWART_LOGIN_SCOPE,
|
||||
}),
|
||||
});
|
||||
const { client_id: clientId } = (await reg.json()) as { client_id: string };
|
||||
const verifier = generateCodeVerifier();
|
||||
const state = 'consent-state';
|
||||
const body = stalwartLoginBody({
|
||||
username: USER,
|
||||
password: PASS,
|
||||
clientId,
|
||||
redirectUri: REGISTERED_REDIRECT_URI,
|
||||
codeChallenge: generateCodeChallenge(verifier),
|
||||
state,
|
||||
});
|
||||
delete body.consent;
|
||||
const first = await fetch(`${SERVER}/api/auth`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const asked = readStalwartLoginResponse(await first.json(), state);
|
||||
expect(asked.kind).toBe('consent_required');
|
||||
if (asked.kind !== 'consent_required') return;
|
||||
|
||||
const answered = await answerConsent(SERVER, asked.token, String(body.query), state);
|
||||
expect(answered.kind).toBe('code');
|
||||
if (answered.kind !== 'code') return;
|
||||
const tokenRes = await fetch(`${SERVER}/auth/token`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
code: answered.code,
|
||||
client_id: clientId,
|
||||
redirect_uri: REGISTERED_REDIRECT_URI,
|
||||
code_verifier: verifier,
|
||||
}).toString(),
|
||||
});
|
||||
const tokens = (await tokenRes.json()) as { access_token?: string; refresh_token?: string };
|
||||
expect(tokens.access_token).toBeTruthy();
|
||||
expect(await sessionStatus(tokens.access_token!)).toBe(200);
|
||||
await revokeRefreshToken(SERVER, {
|
||||
accessToken: tokens.access_token!,
|
||||
refreshToken: tokens.refresh_token,
|
||||
tokenEndpoint: `${SERVER}/auth/token`,
|
||||
clientId,
|
||||
source: 'totp',
|
||||
});
|
||||
}, 30_000);
|
||||
|
||||
it('a refused token exchange is not reported as a wrong password, on both', async () => {
|
||||
// A code the server issued, redeemed with the wrong PKCE verifier: the
|
||||
// token endpoint refuses it after the credentials were accepted.
|
||||
const client = await resolveLoginClient(SERVER);
|
||||
const state = 'exchange-state';
|
||||
const res = await fetch(`${SERVER}/api/auth`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(stalwartLoginBody({
|
||||
username: USER,
|
||||
password: PASS,
|
||||
clientId: client.clientId,
|
||||
redirectUri: client.redirectUri,
|
||||
codeChallenge: generateCodeChallenge(generateCodeVerifier()),
|
||||
state,
|
||||
})),
|
||||
});
|
||||
const outcome = readStalwartLoginResponse(await res.json(), state);
|
||||
expect(outcome.kind).toBe('code');
|
||||
if (outcome.kind !== 'code') return;
|
||||
const token = await fetch(`${SERVER}/auth/token`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({
|
||||
grant_type: 'authorization_code',
|
||||
code: outcome.code,
|
||||
client_id: client.clientId,
|
||||
redirect_uri: client.redirectUri,
|
||||
code_verifier: generateCodeVerifier(),
|
||||
}).toString(),
|
||||
});
|
||||
expect(token.ok).toBe(false);
|
||||
// What exchangePasswordForTokens throws for that answer, and what the
|
||||
// sign-in screen makes of it.
|
||||
const copy = describeLoginError(
|
||||
new TotpLoginError('token_exchange_failed', `Token exchange failed: ${token.status}`),
|
||||
{ serverUrl: SERVER },
|
||||
);
|
||||
expect(copy.detail).toMatch(/password and code were accepted/);
|
||||
}, 30_000);
|
||||
|
||||
it('reads the storage quota the way Settings shows it, on both (#1242)', async () => {
|
||||
const client = new JMAPClient();
|
||||
await client.connect(SERVER, USER, PASS);
|
||||
useSingleton(client);
|
||||
if (!serverSupportsQuota()) return;
|
||||
const res = await client.request([['Quota/get', { accountId: client.accountId }, '0']], [
|
||||
'urn:ietf:params:jmap:core',
|
||||
'urn:ietf:params:jmap:quota',
|
||||
]);
|
||||
const [name, body] = res.methodResponses[0] as unknown as [string, { list: Array<Record<string, number | null>> }];
|
||||
expect(name).toBe('Quota/get');
|
||||
const shown = await fetchMailQuota();
|
||||
expect(shown).toEqual(toMailQuota(body.list as never));
|
||||
if (shown) {
|
||||
const raw = body.list.find((q) => q.resourceType === ('octets' as unknown as number))!;
|
||||
// Never above a softLimit the server set below its hardLimit.
|
||||
if (typeof raw.softLimit === 'number' && raw.softLimit > 0 && typeof raw.hardLimit === 'number' && raw.softLimit < raw.hardLimit) {
|
||||
expect(shown.total).toBe(raw.softLimit);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('account security with the password (Basic) works on both', async () => {
|
||||
const client = new JMAPClient();
|
||||
await client.connect(SERVER, USER, PASS);
|
||||
|
||||
@@ -234,6 +234,25 @@ live('live Stalwart push subscriptions', () => {
|
||||
}
|
||||
}, 60_000);
|
||||
|
||||
it('answers a lookup it cannot serve with an error response, not an empty list (bulwarkmail/webmail#1041)', async () => {
|
||||
// The background task's Email/get for a push names the account the push
|
||||
// came for; one the login can't read must read as a failed lookup (which
|
||||
// shows a generic notification), not as "nothing new".
|
||||
const res = await client.request([['Email/get', { accountId: 'zzzz', ids: ['x'], properties: ['id'] }, '0']]);
|
||||
const [name, body] = res.methodResponses[0] as unknown as [string, { type?: string }];
|
||||
expect(name).toBe('error');
|
||||
expect(typeof body.type).toBe('string');
|
||||
});
|
||||
|
||||
it('takes the Inbox-only filter and echoes it so it compares equal (bulwarkmail/webmail#983)', async () => {
|
||||
const me = client.accountId;
|
||||
const emailPush = await buildEmailPushConfig(undefined, true);
|
||||
expect(emailPush[me].filter).toEqual({ operator: 'AND', conditions: [{ notKeyword: '$junk' }, { inMailbox: inbox }] });
|
||||
const { id } = await create('inbox-only', { emailPush });
|
||||
const listed = (await listPushSubscriptions()).find((s) => s.id === id);
|
||||
expect(sameEmailPush(listed?.emailPush, emailPush), JSON.stringify(listed?.emailPush)).toBe(true);
|
||||
}, 30_000);
|
||||
|
||||
it('refuses push addresses that are not public: http and private IPs everywhere, local names on 1.0', async () => {
|
||||
for (const url of ['http://notifications.relay.bulwarkmail.org/api/push/jmap/x', 'https://192.168.1.20/api/push/jmap/x', 'https://10.0.0.5:8443/api/push/jmap/x']) {
|
||||
const err = await refusal(create('private-ip', { url }));
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
|
||||
// Storage usage counted against hardLimit even where the server set a lower
|
||||
// softLimit, from which writes already fail (RFC 9425 4.1, webmail #1242).
|
||||
|
||||
const quotas: Record<string, unknown>[] = [];
|
||||
vi.mock('../jmap-client', () => ({
|
||||
jmapClient: {
|
||||
accountId: 'a1',
|
||||
currentSession: { capabilities: { 'urn:ietf:params:jmap:quota': {} } },
|
||||
request: vi.fn(async () => ({ methodResponses: [['Quota/get', { list: quotas }, '0']] })),
|
||||
},
|
||||
}));
|
||||
|
||||
import { fetchMailQuota, toMailQuota } from '../quota';
|
||||
|
||||
describe('mail quota', () => {
|
||||
it('counts against softLimit when it is below hardLimit (#1242)', async () => {
|
||||
quotas.splice(0, quotas.length, {
|
||||
id: 'q', resourceType: 'octets', scope: 'account', used: 900, softLimit: 1000, hardLimit: 1200, warnLimit: 800,
|
||||
});
|
||||
await expect(fetchMailQuota()).resolves.toEqual({ used: 900, total: 1000, hardLimit: 1200, warnAt: 800 });
|
||||
});
|
||||
|
||||
it('keeps hardLimit when there is no lower softLimit', () => {
|
||||
expect(toMailQuota([{ id: 'q', resourceType: 'octets', used: 1, hardLimit: 10, softLimit: null }])).toEqual({ used: 1, total: 10 });
|
||||
expect(toMailQuota([{ id: 'q', resourceType: 'octets', used: 1, hardLimit: 10, softLimit: 20 }])).toEqual({ used: 1, total: 10 });
|
||||
expect(toMailQuota([{ id: 'q', resourceType: 'mail', used: 1, limit: 5 }])).toEqual({ used: 1, total: 5 });
|
||||
});
|
||||
});
|
||||
@@ -43,11 +43,28 @@ beforeEach(() => {
|
||||
accounts: {
|
||||
own: { name: 'me', isPersonal: true, isReadOnly: false, accountCapabilities: { [CAPABILITIES.SIEVE]: SIEVE_CAPS } },
|
||||
team: { name: 'Team', isPersonal: false, isReadOnly: false, accountCapabilities: { [CAPABILITIES.MAIL]: {} } },
|
||||
crew: { name: 'Crew', isPersonal: false, isReadOnly: false },
|
||||
other: { name: 'Other', isPersonal: true, isReadOnly: false, accountCapabilities: { [CAPABILITIES.MAIL]: {} } },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('accountSupportsSieve on a shared account that lists its capabilities (#1239)', () => {
|
||||
it('is false when the list leaves this one out', () => {
|
||||
const caps = { [CAPABILITIES.SIEVE]: {}, [CAPABILITIES.MAIL]: {} };
|
||||
expect(accountSupportsSieve(
|
||||
{ name: 'grp', isPersonal: false, isReadOnly: false, accountCapabilities: { [CAPABILITIES.MAIL]: {} } },
|
||||
caps,
|
||||
)).toBe(false);
|
||||
expect(accountSupportsSieve(
|
||||
{ name: 'grp', isPersonal: false, isReadOnly: false, accountCapabilities: { [CAPABILITIES.SIEVE]: {} } },
|
||||
caps,
|
||||
)).toBe(true);
|
||||
// An empty list says nothing: the server-wide answer stands.
|
||||
expect(accountSupportsSieve({ name: 'grp', isPersonal: false, isReadOnly: false, accountCapabilities: {} }, caps)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('accountSupportsSieve', () => {
|
||||
const caps = { [CAPABILITIES.SIEVE]: {} };
|
||||
|
||||
@@ -76,7 +93,9 @@ describe('accountSupportsSieve', () => {
|
||||
describe('isSieveSupported', () => {
|
||||
it('checks the own Sieve account by default and a named account on request', () => {
|
||||
expect(isSieveSupported()).toBe(true);
|
||||
expect(isSieveSupported('team')).toBe(true);
|
||||
// Team lists its capabilities and Sieve isn't one (#1239); Crew lists none.
|
||||
expect(isSieveSupported('team')).toBe(false);
|
||||
expect(isSieveSupported('crew')).toBe(true);
|
||||
expect(isSieveSupported('other')).toBe(false);
|
||||
expect(isSieveSupported('missing')).toBe(false);
|
||||
});
|
||||
|
||||
@@ -30,12 +30,28 @@ beforeEach(() => {
|
||||
capabilities: { [CAPABILITIES.VACATION]: {} },
|
||||
accounts: {
|
||||
own: { name: 'me', isPersonal: true, isReadOnly: false, accountCapabilities: { [CAPABILITIES.VACATION]: {} } },
|
||||
team: { name: 'Team', isPersonal: false, isReadOnly: false, accountCapabilities: { [CAPABILITIES.MAIL]: {} } },
|
||||
team: { name: 'Team', isPersonal: false, isReadOnly: false, accountCapabilities: { [CAPABILITIES.MAIL]: {}, [CAPABILITIES.VACATION]: {} } },
|
||||
other: { name: 'Other', isPersonal: true, isReadOnly: false, accountCapabilities: { [CAPABILITIES.MAIL]: {} } },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('accountSupportsVacation on a shared account that lists its capabilities (#1239)', () => {
|
||||
it('is false when the list leaves this one out', () => {
|
||||
const caps = { [CAPABILITIES.VACATION]: {}, [CAPABILITIES.MAIL]: {} };
|
||||
expect(accountSupportsVacation(
|
||||
{ name: 'grp', isPersonal: false, isReadOnly: false, accountCapabilities: { [CAPABILITIES.MAIL]: {} } },
|
||||
caps,
|
||||
)).toBe(false);
|
||||
expect(accountSupportsVacation(
|
||||
{ name: 'grp', isPersonal: false, isReadOnly: false, accountCapabilities: { [CAPABILITIES.VACATION]: {} } },
|
||||
caps,
|
||||
)).toBe(true);
|
||||
// An empty list says nothing: the server-wide answer stands.
|
||||
expect(accountSupportsVacation({ name: 'grp', isPersonal: false, isReadOnly: false, accountCapabilities: {} }, caps)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('accountSupportsVacation', () => {
|
||||
const caps = { [CAPABILITIES.VACATION]: {} };
|
||||
|
||||
|
||||
@@ -250,8 +250,11 @@ export async function fetchCryptoInfo(): Promise<CryptoInfo> {
|
||||
* Configure Stalwart's encryption at rest (`x:AccountSettings/set`
|
||||
* `encryptionAtRest`). `Aes128`/`Aes256` need a public key id from
|
||||
* {@link fetchPublicKeys}; `encryptOnAppend` also encrypts mail the user
|
||||
* files (sent/imported), `allowSpamTraining` lets the classifier read a
|
||||
* message before it is encrypted.
|
||||
* files (imported or appended over IMAP), `allowSpamTraining` lets the
|
||||
* classifier read a message before it is encrypted. Since Stalwart 1.0.1
|
||||
* JMAP drafts (`$draft`) and the Sent copy of a JMAP submission are stored
|
||||
* unencrypted, so they stay editable and go out readable; 0.16 and 1.0.0
|
||||
* encrypted those too.
|
||||
*/
|
||||
export async function updateEncryptionAtRest(input: {
|
||||
type: EncryptionType;
|
||||
|
||||
@@ -279,11 +279,13 @@ export class JMAPClient {
|
||||
username: string,
|
||||
password: string,
|
||||
totp?: string,
|
||||
/** Cancels the token requests of a TOTP sign-in (the sign-in screen's Cancel). */
|
||||
signal?: AbortSignal,
|
||||
): Promise<JMAPSession> {
|
||||
const baseUrl = serverUrl.replace(/\/+$/, '');
|
||||
const epoch = this.detachSession();
|
||||
if (totp) {
|
||||
return this.connectWithTotp(baseUrl, username, password, totp, epoch);
|
||||
return this.connectWithTotp(baseUrl, username, password, totp, epoch, signal);
|
||||
}
|
||||
const credentials: StoredCredentials = { serverUrl: baseUrl, username, password };
|
||||
this.credentials = credentials;
|
||||
@@ -314,11 +316,12 @@ export class JMAPClient {
|
||||
password: string,
|
||||
totp: string,
|
||||
epoch: number,
|
||||
signal?: AbortSignal,
|
||||
): Promise<JMAPSession> {
|
||||
const { exchangePasswordForTokens, LoginEndpointMissingError } = await import('../lib/totp-login');
|
||||
let tokens: OAuthTokens;
|
||||
try {
|
||||
tokens = await exchangePasswordForTokens(baseUrl, username, password, totp);
|
||||
tokens = await exchangePasswordForTokens(baseUrl, username, password, totp, { signal });
|
||||
} catch (err) {
|
||||
if (err instanceof LoginEndpointMissingError) {
|
||||
this.assertCurrentSession(epoch);
|
||||
|
||||
+34
-11
@@ -8,8 +8,8 @@ interface JMAPQuota {
|
||||
resourceType?: string;
|
||||
used?: number;
|
||||
hardLimit?: number;
|
||||
warnLimit?: number;
|
||||
softLimit?: number;
|
||||
warnLimit?: number | null;
|
||||
softLimit?: number | null;
|
||||
// pre-RFC shape
|
||||
limit?: number;
|
||||
scope?: string;
|
||||
@@ -19,7 +19,38 @@ interface JMAPQuota {
|
||||
|
||||
export interface MailQuota {
|
||||
used: number;
|
||||
/**
|
||||
* The limit the user works against: softLimit when the server sets one
|
||||
* below hardLimit, since writes start failing there (RFC 9425 4.1,
|
||||
* webmail #1242). Otherwise hardLimit.
|
||||
*/
|
||||
total: number;
|
||||
/** hardLimit, only when it is above `total` (inbound mail still lands up to it). */
|
||||
hardLimit?: number;
|
||||
/** warnLimit, only when it is below `total`. */
|
||||
warnAt?: number;
|
||||
}
|
||||
|
||||
const positive = (n: number | null | undefined): n is number =>
|
||||
typeof n === 'number' && Number.isFinite(n) && n > 0;
|
||||
|
||||
/** Picks the mail storage quota out of a Quota/get list (webmail lib/quota.ts toStorageQuota). */
|
||||
export function toMailQuota(quotas: JMAPQuota[]): MailQuota | null {
|
||||
const coversMail = (q: JMAPQuota) =>
|
||||
!q.types?.length || q.types.some((t) => t === 'Email' || t === 'Mail');
|
||||
// Storage quotas use resourceType "octets" (Stalwart, scope "account");
|
||||
// older servers used the pre-RFC "mail" shape.
|
||||
const q =
|
||||
quotas.find((x) => x.resourceType === 'octets' && coversMail(x))
|
||||
|| quotas.find((x) => x.resourceType === 'mail' || x.scope === 'mail');
|
||||
if (!q) return null;
|
||||
const hard = q.hardLimit ?? q.limit ?? 0;
|
||||
const soft = positive(q.softLimit) && (!positive(hard) || q.softLimit < hard) ? q.softLimit : undefined;
|
||||
const total = soft ?? hard;
|
||||
const quota: MailQuota = { used: q.used ?? 0, total };
|
||||
if (soft !== undefined && positive(hard)) quota.hardLimit = hard;
|
||||
if (positive(q.warnLimit) && (!positive(total) || q.warnLimit < total)) quota.warnAt = q.warnLimit;
|
||||
return quota;
|
||||
}
|
||||
|
||||
export function serverSupportsQuota(): boolean {
|
||||
@@ -40,15 +71,7 @@ export async function fetchMailQuota(): Promise<MailQuota | null> {
|
||||
);
|
||||
const [name, body] = res.methodResponses[0] ?? [];
|
||||
if (name !== 'Quota/get') return null;
|
||||
const quotas = (body?.list ?? []) as JMAPQuota[];
|
||||
const coversMail = (q: JMAPQuota) =>
|
||||
!q.types?.length || q.types.some((t) => t === 'Email' || t === 'Mail');
|
||||
const mailQuota =
|
||||
quotas.find((q) => q.resourceType === 'octets' && coversMail(q))
|
||||
|| quotas.find((q) => q.resourceType === 'mail' || q.scope === 'mail');
|
||||
if (!mailQuota) return null;
|
||||
const total = mailQuota.hardLimit ?? mailQuota.limit ?? 0;
|
||||
return { used: mailQuota.used ?? 0, total };
|
||||
return toMailQuota((body?.list ?? []) as JMAPQuota[]);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
+5
-8
@@ -2,6 +2,7 @@ import { jmapClient } from './jmap-client';
|
||||
import { CAPABILITIES, type JMAPAccountInfo } from './types';
|
||||
import { secureFetch } from '../lib/client-cert';
|
||||
import type { SieveScript, SieveCapabilities } from '../lib/sieve/types';
|
||||
import { accountSupports } from '../lib/account-capability';
|
||||
|
||||
// JMAP Sieve (RFC 9661) bindings. Mirrors the webmail JMAPClient Sieve methods
|
||||
// but follows the mobile convention of a functional api/* module driving the
|
||||
@@ -30,18 +31,14 @@ export function getSieveAccountId(): string {
|
||||
|
||||
// Gate on the ACCOUNT capability, not only the server-wide session capability:
|
||||
// RFC 9661 advertises Sieve per account, and an account without Sieve rights
|
||||
// fails every SieveScript call. Stalwart doesn't always advertise capabilities
|
||||
// on shared/group accounts, so treat non-personal accounts as capable - the
|
||||
// rule the webmail's getSharedAccounts() applies. A server that populates no
|
||||
// accountCapabilities at all keeps the session-wide answer.
|
||||
// fails every SieveScript call. A shared account that lists no capabilities
|
||||
// at all, and a server that populates none, keep the session-wide answer
|
||||
// (lib/account-capability, webmail #1239).
|
||||
export function accountSupportsSieve(
|
||||
account: JMAPAccountInfo | undefined,
|
||||
sessionCapabilities: Record<string, unknown> | undefined,
|
||||
): boolean {
|
||||
if (!sessionCapabilities || !(CAPABILITIES.SIEVE in sessionCapabilities)) return false;
|
||||
if (!account) return false;
|
||||
if (!account.isPersonal || !account.accountCapabilities) return true;
|
||||
return CAPABILITIES.SIEVE in account.accountCapabilities;
|
||||
return accountSupports(account, sessionCapabilities, CAPABILITIES.SIEVE);
|
||||
}
|
||||
|
||||
export function isSieveSupported(accountId?: string): boolean {
|
||||
|
||||
+5
-7
@@ -1,5 +1,6 @@
|
||||
import { jmapClient } from './jmap-client';
|
||||
import { CAPABILITIES, type JMAPAccountInfo } from './types';
|
||||
import { accountSupports } from '../lib/account-capability';
|
||||
|
||||
export interface VacationResponse {
|
||||
id: string;
|
||||
@@ -65,17 +66,14 @@ export async function setVacationResponse(
|
||||
|
||||
// Gate on the ACCOUNT capability: RFC 8621 advertises VacationResponse per
|
||||
// account, so the session can list it while a given account lacks it.
|
||||
// Stalwart doesn't always advertise capabilities on shared/group accounts, so
|
||||
// treat non-personal accounts as capable, as the webmail does. A server that
|
||||
// populates no accountCapabilities at all keeps the session-wide answer.
|
||||
// A shared account that lists no capabilities at all, and a server that
|
||||
// populates none, keep the session-wide answer (lib/account-capability,
|
||||
// webmail #1239).
|
||||
export function accountSupportsVacation(
|
||||
account: JMAPAccountInfo | undefined,
|
||||
sessionCapabilities: Record<string, unknown> | undefined,
|
||||
): boolean {
|
||||
if (!sessionCapabilities || !(CAPABILITIES.VACATION in sessionCapabilities)) return false;
|
||||
if (!account) return false;
|
||||
if (!account.isPersonal || !account.accountCapabilities) return true;
|
||||
return CAPABILITIES.VACATION in account.accountCapabilities;
|
||||
return accountSupports(account, sessionCapabilities, CAPABILITIES.VACATION);
|
||||
}
|
||||
|
||||
export function isVacationSupported(accountId?: string): boolean {
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { TextInput as RNTextInput, View, Text, StyleSheet, ViewStyle, TextInputProps } from 'react-native';
|
||||
import { spacing, radius, typography, componentSizes, type ThemePalette } from '../theme/tokens';
|
||||
import { useColors } from '../theme/colors';
|
||||
import { inputAccessibility } from './input-a11y';
|
||||
|
||||
interface InputProps extends TextInputProps {
|
||||
label?: string;
|
||||
@@ -48,6 +49,7 @@ export default function Input({
|
||||
<RNTextInput
|
||||
ref={ref}
|
||||
{...props}
|
||||
{...inputAccessibility({ label, error, placeholder: props.placeholder, accessibilityLabel: props.accessibilityLabel, accessibilityHint: props.accessibilityHint })}
|
||||
style={[styles.input, style]}
|
||||
placeholderTextColor={c.mutedForeground}
|
||||
onFocus={(e) => { setFocused(true); props.onFocus?.(e); }}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useOutboxStore } from '../stores/outbox-store';
|
||||
import { useLocaleStore } from '../stores/locale-store';
|
||||
import { spacing, typography, type ThemePalette } from '../theme/tokens';
|
||||
import { useColors } from '../theme/colors';
|
||||
import { announceConnectivity } from '../lib/announce';
|
||||
|
||||
interface OfflineBannerProps {
|
||||
/** Optional extra notice (e.g. "Showing cached mail"). */
|
||||
@@ -19,6 +20,11 @@ export function OfflineBanner({ hint }: OfflineBannerProps) {
|
||||
const t = useLocaleStore((s) => s.t);
|
||||
// Pending offline mutations that will replay once we're back online.
|
||||
const queued = useOutboxStore((s) => s.entries.length);
|
||||
const offlineLabel = t('offline.banner', 'You are offline');
|
||||
// VoiceOver hears the drop too (Android reads the live region below).
|
||||
React.useEffect(() => {
|
||||
announceConnectivity(online, offlineLabel);
|
||||
}, [online, offlineLabel]);
|
||||
if (online) return null;
|
||||
const queuedHint = queued > 0
|
||||
? t(
|
||||
@@ -28,7 +34,7 @@ export function OfflineBanner({ hint }: OfflineBannerProps) {
|
||||
)
|
||||
: null;
|
||||
const suffix = [hint, queuedHint].filter(Boolean).join(' · ');
|
||||
const label = t('offline.banner', 'You are offline');
|
||||
const label = offlineLabel;
|
||||
return (
|
||||
<View style={styles.bar} accessibilityRole="alert" accessibilityLiveRegion="polite">
|
||||
<CloudOff size={14} color={c.warningForeground} />
|
||||
|
||||
@@ -80,6 +80,11 @@ function ToastCard({ toast }: { toast: Toast }) {
|
||||
// screen change remounts the card). The countdown line runs out with it;
|
||||
// with animations off it stays full instead of moving.
|
||||
React.useEffect(() => {
|
||||
// "Until I close them": no timer, and the line stays full.
|
||||
if (toast.duration <= 0) {
|
||||
remainingShare.setValue(1);
|
||||
return undefined;
|
||||
}
|
||||
const remaining = Math.max(0, toast.duration - (Date.now() - toast.createdAt));
|
||||
const timer = setTimeout(() => removeToast(toast.id), remaining);
|
||||
if (!animate) {
|
||||
|
||||
@@ -11,7 +11,11 @@ import { useEmailStore } from '../stores/email-store';
|
||||
import { useSendUndoStore, restoreUndoneSend, type PendingUndoSend } from '../stores/send-undo-store';
|
||||
import { useLocaleStore } from '../stores/locale-store';
|
||||
import type { RootStackParamList } from '../navigation/types';
|
||||
import { announce } from '../lib/announce';
|
||||
import { lastingToastDuration } from '../lib/toast-duration';
|
||||
|
||||
// At least this long; the "Notification duration" setting can make it
|
||||
// longer, or keep the bar until it is replaced (lib/toast-duration).
|
||||
const VISIBLE_MS = 5000;
|
||||
|
||||
interface Shown {
|
||||
@@ -48,7 +52,7 @@ export function UndoSnackbar() {
|
||||
// the bar; the newer one wins.
|
||||
const current = React.useMemo<Shown | null>(() => {
|
||||
const list: Shown | null = entry
|
||||
? { label: entry.label, createdAt: entry.createdAt, visibleMs: VISIBLE_MS }
|
||||
? { label: entry.label, createdAt: entry.createdAt, visibleMs: lastingToastDuration(VISIBLE_MS) }
|
||||
: null;
|
||||
const send: Shown | null = pendingSend
|
||||
? {
|
||||
@@ -70,6 +74,14 @@ export function UndoSnackbar() {
|
||||
if (current) setShown(current);
|
||||
}, [current]);
|
||||
|
||||
// Read the bar out on iOS, which has no live regions; Android reads the
|
||||
// bar's live region. Once per entry, not on every countdown re-render.
|
||||
const undoLabel = t('common.undo', 'Undo');
|
||||
React.useEffect(() => {
|
||||
if (!current) return;
|
||||
announce(current.label);
|
||||
}, [current?.createdAt, current?.label]);
|
||||
|
||||
// Drive the auto-dismiss off the entry's createdAt so re-renders during
|
||||
// animation don't reset the timer, and so consecutive actions reset it.
|
||||
React.useEffect(() => {
|
||||
@@ -88,6 +100,8 @@ export function UndoSnackbar() {
|
||||
Animated.timing(opacity, { toValue: 1, duration: 220, useNativeDriver: true }),
|
||||
]).start();
|
||||
|
||||
// Kept until replaced or used ("Until I close them").
|
||||
if (current.visibleMs <= 0) return undefined;
|
||||
const elapsed = Date.now() - current.createdAt;
|
||||
const remaining = Math.max(0, current.visibleMs - elapsed);
|
||||
const id = setTimeout(() => {
|
||||
@@ -133,13 +147,15 @@ export function UndoSnackbar() {
|
||||
{ left: frame.left, right: frame.right, bottom: insets.bottom + frame.bottom, opacity, transform: [{ translateY: slideY }] },
|
||||
]}
|
||||
>
|
||||
<View style={styles.bar}>
|
||||
<View style={styles.bar} accessibilityLiveRegion="polite">
|
||||
<Text style={styles.label} numberOfLines={1}>{shown.label}</Text>
|
||||
{shown.send && (
|
||||
<Pressable
|
||||
onPress={onSendNow}
|
||||
hitSlop={8}
|
||||
disabled={sendBusy}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('email_composer.send_now', 'Send now')}
|
||||
style={({ pressed }) => [styles.undoBtn, pressed && styles.undoBtnPressed]}
|
||||
>
|
||||
<Text style={styles.sendNowText}>{t('email_composer.send_now', 'Send now').toUpperCase()}</Text>
|
||||
@@ -149,6 +165,8 @@ export function UndoSnackbar() {
|
||||
onPress={() => { void onUndo(); }}
|
||||
hitSlop={8}
|
||||
disabled={sendBusy}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={undoLabel}
|
||||
style={({ pressed }) => [styles.undoBtn, pressed && styles.undoBtnPressed]}
|
||||
>
|
||||
<Text style={styles.undoText}>{t('common.undo', 'Undo').toUpperCase()}</Text>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { densityPreviewConfig } from '../settings/density-preview';
|
||||
|
||||
describe('density sample', () => {
|
||||
it('shows preview lines only when "Show preview text" is on (webmail c166651d1)', () => {
|
||||
expect(densityPreviewConfig('regular', true).showPreview).toBe(true);
|
||||
expect(densityPreviewConfig('regular', false).showPreview).toBe(false);
|
||||
expect(densityPreviewConfig('comfortable', false).showPreview).toBe(false);
|
||||
// Compact densities have no room for one either way.
|
||||
expect(densityPreviewConfig('compact', true).showPreview).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
// Text fields need a name a screen reader can say (webmail ef13556a0,
|
||||
// dcc21acd0, 4e2affe40, f73643bbb). The password, code and new-password
|
||||
// fields under Security had none: no label prop, no placeholder, no
|
||||
// accessibilityLabel. Settings and the sign-in steps are scanned so that a
|
||||
// bare field doesn't creep back in; Input itself names its field from its
|
||||
// label, then its placeholder.
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { readdirSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { inputAccessibility } from '../input-a11y';
|
||||
|
||||
const SRC = join(__dirname, '..', '..');
|
||||
|
||||
function elementEnd(source: string, from: number): number {
|
||||
let depth = 0;
|
||||
for (let i = from; i < source.length; i++) {
|
||||
const ch = source[i];
|
||||
if (ch === '{') depth += 1;
|
||||
else if (ch === '}') depth -= 1;
|
||||
else if (ch === '/' && depth === 0 && source[i + 1] === '>') return i;
|
||||
}
|
||||
return source.length;
|
||||
}
|
||||
|
||||
/** `file:line` of every TextInput or Input element without a name. */
|
||||
function unnamedFields(file: string): string[] {
|
||||
const source = readFileSync(join(SRC, file), 'utf8');
|
||||
const out: string[] = [];
|
||||
for (const m of source.matchAll(/<(TextInput|Input)\b/g)) {
|
||||
const element = source.slice(m.index!, elementEnd(source, m.index! + m[0].length));
|
||||
if (/\baccessibilityLabel=/.test(element)) continue;
|
||||
if (m[1] === 'Input' && /\blabel=/.test(element)) continue;
|
||||
out.push(`${file}:${source.slice(0, m.index!).split('\n').length}`);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const SETTINGS = readdirSync(join(SRC, 'components', 'settings'))
|
||||
.filter((name) => name.endsWith('.tsx'))
|
||||
// The calendar pass owns the auto-reply editor.
|
||||
.filter((name) => name !== 'VacationSettings.tsx')
|
||||
.map((name) => `components/settings/${name}`);
|
||||
|
||||
const FILES = [
|
||||
...SETTINGS,
|
||||
'screens/SettingsScreen.tsx',
|
||||
'screens/login/EmailStep.tsx',
|
||||
'screens/login/PasswordStep.tsx',
|
||||
'screens/login/ServerStep.tsx',
|
||||
];
|
||||
|
||||
describe('field names', () => {
|
||||
it('every field in Settings and the sign-in steps has a name', () => {
|
||||
expect(FILES.flatMap(unnamedFields)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('inputAccessibility', () => {
|
||||
it('names the field after its label', () => {
|
||||
expect(inputAccessibility({ label: 'Folder name', placeholder: 'Receipts' })).toEqual({ accessibilityLabel: 'Folder name' });
|
||||
});
|
||||
|
||||
it('falls back to the placeholder, and lets an explicit name win', () => {
|
||||
expect(inputAccessibility({ placeholder: 'Search' })).toEqual({ accessibilityLabel: 'Search' });
|
||||
expect(inputAccessibility({ label: 'Name', accessibilityLabel: 'Display name' })).toEqual({ accessibilityLabel: 'Display name' });
|
||||
});
|
||||
|
||||
it('reads the error as the hint', () => {
|
||||
expect(inputAccessibility({ label: 'Email', error: 'Not an address' })).toEqual({
|
||||
accessibilityLabel: 'Email',
|
||||
accessibilityHint: 'Not an address',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { identityDraftChanged } from '../settings/identity-draft';
|
||||
|
||||
const opened = { name: 'Ada', email: 'ada@example.com', replyTo: '', bcc: '', textSignature: '', htmlSignature: '' };
|
||||
|
||||
describe('identity editor', () => {
|
||||
it('asks before closing only when something changed (webmail 9d3ae2203)', () => {
|
||||
expect(identityDraftChanged(opened, { ...opened })).toBe(false);
|
||||
expect(identityDraftChanged(opened, { ...opened, textSignature: '-- \nAda' })).toBe(true);
|
||||
expect(identityDraftChanged(opened, { ...opened, bcc: 'archive@example.com' })).toBe(true);
|
||||
expect(identityDraftChanged(null, opened)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,7 @@ import { makeIcon } from './icon';
|
||||
export type { AppIcon, IconProps } from './icon';
|
||||
|
||||
const G = {
|
||||
'accessible': ["M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0","M10 16.5l2 -3l2 3m-2 -3v-2l3 -1m-6 0l3 1","M11.5 7.5a.5 .5 0 1 0 1 0a.5 .5 0 1 0 -1 0"],
|
||||
'address-book': ["M20 6v12a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2","M10 16h6","M11 11a2 2 0 1 0 4 0a2 2 0 1 0 -4 0","M4 8h3","M4 12h3","M4 16h3"],
|
||||
'alarm': ["M5 13a7 7 0 1 0 14 0a7 7 0 1 0 -14 0","M12 10l0 3l2 0","M7 4l-2.75 2","M17 4l2.75 2"],
|
||||
'alert-circle': ["M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0","M12 8v4","M12 16h.01"],
|
||||
@@ -176,6 +177,7 @@ const G = {
|
||||
'zoom-scan': ["M8 11a3 3 0 1 0 6 0a3 3 0 0 0 -6 0","M16 16l-2.5 -2.5","M3 7v-2a2 2 0 0 1 2 -2h2","M3 17v2a2 2 0 0 0 2 2h2","M17 3h2a2 2 0 0 1 2 2v2","M17 21h2a2 2 0 0 0 2 -2v-2"],
|
||||
} as const;
|
||||
|
||||
export const Accessibility = makeIcon('Accessibility', G['accessible']);
|
||||
export const AlarmClock = makeIcon('AlarmClock', G['alarm']);
|
||||
export const AlertCircle = makeIcon('AlertCircle', G['alert-circle']);
|
||||
export const AlertTriangle = makeIcon('AlertTriangle', G['alert-triangle']);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// The name a screen reader gives a text field (webmail components/ui/field.tsx,
|
||||
// ef13556a0, dcc21acd0, 4e2affe40). Input drew its label as a Text of its
|
||||
// own, not tied to the field, so TalkBack and VoiceOver announced an
|
||||
// unnamed "edit box"; a placeholder alone stops being read once the field
|
||||
// has a value. The label names the field, then the placeholder; an error
|
||||
// is read as the hint so it is heard on focus.
|
||||
|
||||
export interface InputAccessibilityProps {
|
||||
label?: string;
|
||||
error?: string;
|
||||
placeholder?: string;
|
||||
accessibilityLabel?: string;
|
||||
accessibilityHint?: string;
|
||||
}
|
||||
|
||||
export function inputAccessibility(props: InputAccessibilityProps): {
|
||||
accessibilityLabel?: string;
|
||||
accessibilityHint?: string;
|
||||
} {
|
||||
const name = props.accessibilityLabel || props.label || props.placeholder || undefined;
|
||||
const hint = props.error || props.accessibilityHint || undefined;
|
||||
return {
|
||||
...(name ? { accessibilityLabel: name } : {}),
|
||||
...(hint ? { accessibilityHint: hint } : {}),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { SettingsSection, SettingItem, Select, ToggleSwitch } from './settings-section';
|
||||
import Button from '../Button';
|
||||
import { spacing } from '../../theme/tokens';
|
||||
import { useLocaleStore } from '../../stores/locale-store';
|
||||
import { FONT_SIZES, useSettingsStore, type FontSize } from '../../stores/settings-store';
|
||||
import { toast } from '../../stores/toast-store';
|
||||
import {
|
||||
ACCESSIBILITY_PRESET_IDS,
|
||||
applyPresetChanges,
|
||||
presetChanges,
|
||||
undoPresetChanges,
|
||||
type AccessibilityPresetId,
|
||||
} from '../../lib/accessibility-presets';
|
||||
import { TOAST_DURATION_CHOICES } from '../../lib/toast-duration';
|
||||
|
||||
// Percentages read the same in every language the app has.
|
||||
const TEXT_SIZE_LABELS: Record<FontSize, string> = {
|
||||
small: '87.5%',
|
||||
medium: '100%',
|
||||
large: '112.5%',
|
||||
'x-large': '125%',
|
||||
'xx-large': '150%',
|
||||
'xxx-large': '175%',
|
||||
huge: '200%',
|
||||
};
|
||||
|
||||
type Translate = (key: string, fallback?: string, params?: Record<string, string | number>) => string;
|
||||
|
||||
// Literal keys, so the harvest script and the key test see them.
|
||||
function presetCopy(id: AccessibilityPresetId, t: Translate): { label: string; description: string } {
|
||||
switch (id) {
|
||||
case 'screen-reader':
|
||||
return {
|
||||
label: t('settings.accessibility.presets.screen-reader.label', 'Screen reader'),
|
||||
description: t('settings.accessibility.presets.screen-reader.description_mobile', 'No conversation grouping, so every message is a row of its own.'),
|
||||
};
|
||||
case 'low-vision':
|
||||
return {
|
||||
label: t('settings.accessibility.presets.low-vision.label', 'Low vision'),
|
||||
description: t('settings.accessibility.presets.low-vision.description_mobile', 'Text at 150% and labels under the toolbar icons.'),
|
||||
};
|
||||
case 'motion':
|
||||
return {
|
||||
label: t('settings.accessibility.presets.motion.label', 'Motion and seizure safe'),
|
||||
description: t('settings.accessibility.presets.motion.description_mobile', 'No animations or sliding transitions.'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings > Accessibility (webmail components/settings/accessibility-settings.tsx):
|
||||
* the presets a phone can apply, text size, motion, and the timing and
|
||||
* confirmation options. Reduce motion also follows the system setting
|
||||
* (theme/dynamic useShouldAnimate).
|
||||
*/
|
||||
export function AccessibilitySettings() {
|
||||
const t = useLocaleStore((s) => s.t);
|
||||
const hydrated = useSettingsStore((s) => s.hydrated);
|
||||
const hydrate = useSettingsStore((s) => s.hydrate);
|
||||
const update = useSettingsStore((s) => s.updateSetting);
|
||||
const fontSize = useSettingsStore((s) => s.fontSize);
|
||||
const animationsEnabled = useSettingsStore((s) => s.animationsEnabled);
|
||||
const toastDuration = useSettingsStore((s) => s.toastDuration);
|
||||
const sendConfirmation = useSettingsStore((s) => s.sendConfirmation);
|
||||
const confirmDelete = useSettingsStore((s) => s.confirmDelete);
|
||||
const confirmArchive = useSettingsStore((s) => s.confirmArchive);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!hydrated) void hydrate();
|
||||
}, [hydrated, hydrate]);
|
||||
|
||||
const presetName = (id: AccessibilityPresetId) => presetCopy(id, t).label;
|
||||
|
||||
const applyPreset = (id: AccessibilityPresetId) => {
|
||||
const name = presetName(id);
|
||||
const changes = presetChanges(id, useSettingsStore.getState());
|
||||
if (changes.length === 0) {
|
||||
toast.info(t('settings.accessibility.presets.nothing_changed', '{name}: your settings already match.', { name }));
|
||||
return;
|
||||
}
|
||||
applyPresetChanges(changes, update);
|
||||
toast.success(
|
||||
t('settings.accessibility.presets.applied_mobile', '{name} changed {count, plural, one {# setting} other {# settings}}', {
|
||||
name,
|
||||
count: changes.length,
|
||||
}),
|
||||
{
|
||||
action: {
|
||||
label: t('settings.accessibility.presets.undo', 'Undo'),
|
||||
onPress: () => undoPresetChanges(changes, useSettingsStore.getState(), update),
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const durationLabel = (value: number) => {
|
||||
if (value < 0) return t('settings.accessibility.toast_duration.automatic', 'Automatic (longer with a screen reader)');
|
||||
if (value === 0) return t('settings.accessibility.toast_duration.until_closed', 'Until I close them');
|
||||
return t('settings.accessibility.toast_duration.seconds', '{seconds} seconds', { seconds: value });
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={{ gap: spacing.xxxl }}>
|
||||
<SettingsSection
|
||||
title={t('settings.accessibility.presets.title', 'Presets')}
|
||||
description={t('settings.accessibility.presets.description', 'Each preset changes several settings at once. Afterwards they are normal settings you can change one by one.')}
|
||||
>
|
||||
{ACCESSIBILITY_PRESET_IDS.map((id, i) => (
|
||||
<SettingItem
|
||||
key={id}
|
||||
label={presetName(id)}
|
||||
description={presetCopy(id, t).description}
|
||||
noBorder={i === ACCESSIBILITY_PRESET_IDS.length - 1}
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onPress={() => applyPreset(id)}
|
||||
accessibilityLabel={t('settings.accessibility.presets.apply_named', 'Apply {name}', { name: presetName(id) })}
|
||||
>
|
||||
{t('settings.accessibility.presets.apply', 'Apply')}
|
||||
</Button>
|
||||
</SettingItem>
|
||||
))}
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection title={t('settings.accessibility.sections.text', 'Text and reading')}>
|
||||
<SettingItem
|
||||
label={t('settings.accessibility.text_size.label', 'Text size')}
|
||||
description={t('settings.accessibility.text_size.description_mobile', "Scales the app's text on top of your phone's text size. 100% is your phone's size.")}
|
||||
noBorder
|
||||
>
|
||||
<Select
|
||||
value={fontSize}
|
||||
onChange={(v) => update('fontSize', v as FontSize)}
|
||||
options={FONT_SIZES.map((size) => ({ value: size, label: TEXT_SIZE_LABELS[size] }))}
|
||||
/>
|
||||
</SettingItem>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection title={t('settings.accessibility.sections.motion', 'Motion')}>
|
||||
<SettingItem
|
||||
label={t('settings.accessibility.reduce_motion.label', 'Reduce motion')}
|
||||
description={t('settings.accessibility.reduce_motion.description_mobile', "Turns off sliding panels and transitions. The app also does this when reduced motion is on in your phone's settings.")}
|
||||
noBorder
|
||||
>
|
||||
<ToggleSwitch
|
||||
checked={!animationsEnabled}
|
||||
onChange={(reduce) => update('animationsEnabled', !reduce)}
|
||||
accessibilityLabel={t('settings.accessibility.reduce_motion.label', 'Reduce motion')}
|
||||
/>
|
||||
</SettingItem>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection title={t('settings.accessibility.sections.timing', 'Timing and mistakes')}>
|
||||
<SettingItem
|
||||
label={t('settings.accessibility.toast_duration.label', 'Notification duration')}
|
||||
description={t('settings.accessibility.toast_duration.description', 'How long messages such as "Moved to Archive" with their Undo button stay on screen.')}
|
||||
>
|
||||
<Select
|
||||
value={String(toastDuration)}
|
||||
onChange={(v) => update('toastDuration', Number(v))}
|
||||
options={TOAST_DURATION_CHOICES.map((value) => ({ value: String(value), label: durationLabel(value) }))}
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem
|
||||
label={t('settings.accessibility.confirm_send.label', 'Confirm before sending')}
|
||||
description={t('settings.accessibility.confirm_send.description', 'Asks before a message is sent.')}
|
||||
>
|
||||
<ToggleSwitch
|
||||
checked={sendConfirmation}
|
||||
onChange={(v) => update('sendConfirmation', v)}
|
||||
accessibilityLabel={t('settings.accessibility.confirm_send.label', 'Confirm before sending')}
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem
|
||||
label={t('settings.accessibility.confirm_delete.label', 'Confirm before deleting')}
|
||||
description={t('settings.accessibility.confirm_delete.description', 'Asks before a message is deleted, also for single messages.')}
|
||||
>
|
||||
<ToggleSwitch
|
||||
checked={confirmDelete}
|
||||
onChange={(v) => update('confirmDelete', v)}
|
||||
accessibilityLabel={t('settings.accessibility.confirm_delete.label', 'Confirm before deleting')}
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem
|
||||
label={t('settings.accessibility.confirm_archive.label', 'Confirm before archiving')}
|
||||
description={t('settings.accessibility.confirm_archive.description', 'Asks before a message is archived.')}
|
||||
noBorder
|
||||
>
|
||||
<ToggleSwitch
|
||||
checked={confirmArchive}
|
||||
onChange={(v) => update('confirmArchive', v)}
|
||||
accessibilityLabel={t('settings.accessibility.confirm_archive.label', 'Confirm before archiving')}
|
||||
/>
|
||||
</SettingItem>
|
||||
</SettingsSection>
|
||||
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -178,7 +178,7 @@ function PasswordChangeSection({ otpEnabled }: { otpEnabled: boolean }) {
|
||||
<View style={styles.pwField}>
|
||||
<Text style={styles.pwLabel}>{t('settings.security.password.current', "Current password")}</Text>
|
||||
<View style={styles.pwInputRow}>
|
||||
<Input value={currentPwd} onChangeText={setCurrentPwd} secureTextEntry={!showCurrent} autoCapitalize="none" containerStyle={{ flex: 1 }} />
|
||||
<Input accessibilityLabel={t('settings.security.password.current', "Current password")} value={currentPwd} onChangeText={setCurrentPwd} secureTextEntry={!showCurrent} autoCapitalize="none" containerStyle={{ flex: 1 }} />
|
||||
<Pressable style={styles.eyeBtn} onPress={() => setShowCurrent((v) => !v)} accessibilityRole="button" accessibilityLabel={t('settings.security.password.toggle_visibility', "Show or hide password")}>
|
||||
{showCurrent ? <EyeOff size={16} color={c.mutedForeground} /> : <Eye size={16} color={c.mutedForeground} />}
|
||||
</Pressable>
|
||||
@@ -188,14 +188,14 @@ function PasswordChangeSection({ otpEnabled }: { otpEnabled: boolean }) {
|
||||
{otpEnabled && (
|
||||
<View style={styles.pwField}>
|
||||
<Text style={styles.pwLabel}>{t('settings.security.two_factor.code', "Verification code")}</Text>
|
||||
<Input value={otpCode} onChangeText={setOtpCode} keyboardType="number-pad" maxLength={6} textContentType="oneTimeCode" autoComplete="one-time-code" />
|
||||
<Input accessibilityLabel={t('settings.security.two_factor.code', "Verification code")} value={otpCode} onChangeText={setOtpCode} keyboardType="number-pad" maxLength={6} textContentType="oneTimeCode" autoComplete="one-time-code" />
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={styles.pwField}>
|
||||
<Text style={styles.pwLabel}>{t('settings.security.password.new', "New password")}</Text>
|
||||
<View style={styles.pwInputRow}>
|
||||
<Input value={newPwd} onChangeText={setNewPwd} secureTextEntry={!showNew} autoCapitalize="none" containerStyle={{ flex: 1 }} />
|
||||
<Input accessibilityLabel={t('settings.security.password.new', "New password")} value={newPwd} onChangeText={setNewPwd} secureTextEntry={!showNew} autoCapitalize="none" containerStyle={{ flex: 1 }} />
|
||||
<Pressable style={styles.eyeBtn} onPress={() => setShowNew((v) => !v)} accessibilityRole="button" accessibilityLabel={t('settings.security.password.toggle_visibility', "Show or hide password")}>
|
||||
{showNew ? <EyeOff size={16} color={c.mutedForeground} /> : <Eye size={16} color={c.mutedForeground} />}
|
||||
</Pressable>
|
||||
@@ -204,7 +204,7 @@ function PasswordChangeSection({ otpEnabled }: { otpEnabled: boolean }) {
|
||||
|
||||
<View style={styles.pwField}>
|
||||
<Text style={styles.pwLabel}>{t('settings.security.password.confirm', "Confirm password")}</Text>
|
||||
<Input value={confirmPwd} onChangeText={setConfirmPwd} secureTextEntry={!showNew} autoCapitalize="none" />
|
||||
<Input accessibilityLabel={t('settings.security.password.confirm', "Confirm password")} value={confirmPwd} onChangeText={setConfirmPwd} secureTextEntry={!showNew} autoCapitalize="none" />
|
||||
</View>
|
||||
|
||||
{error && <Text style={styles.errorText}>{error}</Text>}
|
||||
@@ -252,7 +252,7 @@ function DisplayNameSection({ initial, onSaved }: { initial: string; onSaved: (n
|
||||
return (
|
||||
<SettingItem label={t('settings.security.display_name.label', "Display name")} description={t('settings.security.display_name.description', "The name shown on this account.")}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: spacing.sm }}>
|
||||
<Input value={name} onChangeText={setName} placeholder={t('settings.security.display_name.label', "Display name")} containerStyle={{ width: 150 }} />
|
||||
<Input accessibilityLabel={t('settings.security.display_name.label', "Display name")} value={name} onChangeText={setName} placeholder={t('settings.security.display_name.label', "Display name")} containerStyle={{ width: 150 }} />
|
||||
<Button size="sm" loading={saving} disabled={saving || name === initial} onPress={() => { void save(); }}>
|
||||
{saved ? <Check size={14} color={c.primaryForeground} /> : t('common.save', "Save")}
|
||||
</Button>
|
||||
@@ -356,11 +356,11 @@ function TotpSection({ enabled, onChanged }: { enabled: boolean; onChanged: () =
|
||||
</View>
|
||||
<View>
|
||||
<Text style={styles.pwLabel}>{t('settings.security.password.current', "Current password")}</Text>
|
||||
<Input value={password} onChangeText={setPassword} secureTextEntry autoCapitalize="none" />
|
||||
<Input accessibilityLabel={t('settings.security.password.current', "Current password")} value={password} onChangeText={setPassword} secureTextEntry autoCapitalize="none" />
|
||||
</View>
|
||||
<View>
|
||||
<Text style={styles.pwLabel}>{t('settings.security.two_factor.code', "Verification code")}</Text>
|
||||
<Input value={otpCode} onChangeText={setOtpCode} keyboardType="number-pad" maxLength={6} />
|
||||
<Input accessibilityLabel={t('settings.security.two_factor.code', "Verification code")} value={otpCode} onChangeText={setOtpCode} keyboardType="number-pad" maxLength={6} />
|
||||
</View>
|
||||
{error ? <Text style={styles.errorText}>{error}</Text> : null}
|
||||
<View style={styles.rowGap}>
|
||||
@@ -375,8 +375,8 @@ function TotpSection({ enabled, onChanged }: { enabled: boolean; onChanged: () =
|
||||
{disableOpen && (
|
||||
<View style={styles.panel}>
|
||||
<Text style={styles.panelHint}>{t('settings.security.two_factor.disable_hint_code', "Enter your password and a code from your authenticator app to turn off two-factor authentication.")}</Text>
|
||||
<Input value={password} onChangeText={setPassword} secureTextEntry autoCapitalize="none" placeholder={t('settings.security.password.current', "Current password")} />
|
||||
<Input value={otpCode} onChangeText={setOtpCode} keyboardType="number-pad" maxLength={6} textContentType="oneTimeCode" autoComplete="one-time-code" placeholder={t('settings.security.two_factor.code', "Verification code")} />
|
||||
<Input accessibilityLabel={t('settings.security.password.current', "Current password")} value={password} onChangeText={setPassword} secureTextEntry autoCapitalize="none" placeholder={t('settings.security.password.current', "Current password")} />
|
||||
<Input accessibilityLabel={t('settings.security.two_factor.code', "Verification code")} value={otpCode} onChangeText={setOtpCode} keyboardType="number-pad" maxLength={6} textContentType="oneTimeCode" autoComplete="one-time-code" placeholder={t('settings.security.two_factor.code', "Verification code")} />
|
||||
{error ? <Text style={styles.errorText}>{error}</Text> : null}
|
||||
<View style={styles.rowGap}>
|
||||
<Button variant="destructive" size="sm" loading={saving} disabled={saving || !password || !otpCode.trim()} onPress={() => { void confirmDisable(); }}>
|
||||
@@ -488,7 +488,7 @@ function CredentialSection({ icon: Icon, title, description, nameLabel, namePlac
|
||||
<View style={styles.panel}>
|
||||
<View>
|
||||
<Text style={styles.pwLabel}>{nameLabel}</Text>
|
||||
<Input value={name} onChangeText={setName} placeholder={namePlaceholder} />
|
||||
<Input accessibilityLabel={nameLabel} value={name} onChangeText={setName} placeholder={namePlaceholder} />
|
||||
</View>
|
||||
<View>
|
||||
<Text style={styles.pwLabel}>{t('settings.security.credentials.expires', "Expires (optional)")}</Text>
|
||||
@@ -508,7 +508,7 @@ function CredentialSection({ icon: Icon, title, description, nameLabel, namePlac
|
||||
</View>
|
||||
<View>
|
||||
<Text style={styles.pwLabel}>{t('settings.security.credentials.allowed_ips', "Allowed IPs (optional)")}</Text>
|
||||
<Input value={allowedIpsRaw} onChangeText={setAllowedIpsRaw} placeholder="1.2.3.4, 10.0.0.0/8" autoCapitalize="none" />
|
||||
<Input accessibilityLabel={t('settings.security.credentials.allowed_ips', "Allowed IPs (optional)")} value={allowedIpsRaw} onChangeText={setAllowedIpsRaw} placeholder="1.2.3.4, 10.0.0.0/8" autoCapitalize="none" />
|
||||
<Text style={styles.fieldHint}>{t('settings.security.credentials.allowed_ips_hint', "Comma or space separated. Leave empty to allow any IP.")}</Text>
|
||||
</View>
|
||||
<View style={styles.rowGap}>
|
||||
@@ -656,11 +656,11 @@ function PublicKeysSection({
|
||||
<View style={styles.panel}>
|
||||
<View>
|
||||
<Text style={styles.pwLabel}>{t('settings.security.public_keys.name_label', 'Public Key name')}</Text>
|
||||
<Input value={name} onChangeText={setName} placeholder={t('settings.security.public_keys.name_placeholder', 'e.g. My Key')} />
|
||||
<Input accessibilityLabel={t('settings.security.public_keys.name_label', 'Public Key name')} value={name} onChangeText={setName} placeholder={t('settings.security.public_keys.name_placeholder', 'e.g. My Key')} />
|
||||
</View>
|
||||
<View>
|
||||
<Text style={styles.pwLabel}>{t('settings.security.public_keys.key_label', 'Public Key (S/MIME or PGP ASCII-armored)')}</Text>
|
||||
<Input
|
||||
<Input accessibilityLabel={t('settings.security.public_keys.key_label', 'Public Key (S/MIME or PGP ASCII-armored)')}
|
||||
value={keyText}
|
||||
onChangeText={setKeyText}
|
||||
placeholder={t('settings.security.public_keys.key_placeholder', 'Paste your public key here')}
|
||||
|
||||
@@ -15,7 +15,8 @@ import { setPendingSettingsTab } from '../../navigation/pending-settings-tab';
|
||||
import { sharedAccountSettingsTabs } from '../../lib/capabilities';
|
||||
import { jmapClient } from '../../api/jmap-client';
|
||||
import { fetchMailQuota, type MailQuota } from '../../api/quota';
|
||||
import { MAX_ACCOUNTS } from '../../lib/account-utils';
|
||||
import { MAX_ACCOUNTS, accountRowText } from '../../lib/account-utils';
|
||||
import { toUnicodeEmail } from '../../lib/idn';
|
||||
import type { RootStackParamList } from '../../navigation/types';
|
||||
|
||||
function formatFileSize(bytes: number): string {
|
||||
@@ -168,7 +169,8 @@ export function AccountSettings() {
|
||||
{accounts.map((a, index) => {
|
||||
const isActive = a.id === activeAccountId;
|
||||
const isDefault = a.id === defaultAccountId;
|
||||
const label = a.email || a.username;
|
||||
// The address leads when another account has the same name.
|
||||
const rowText = accountRowText(a, accounts);
|
||||
return (
|
||||
<View key={a.id} style={[styles.row, index > 0 && styles.rowBorder]}>
|
||||
<View style={[styles.avatar, { backgroundColor: a.avatarColor }]}>
|
||||
@@ -183,10 +185,10 @@ export function AccountSettings() {
|
||||
: t('settings.account.accounts.switch_to', 'Switch to this account')}
|
||||
>
|
||||
<View style={styles.rowTitle}>
|
||||
<Text style={styles.rowName} numberOfLines={1}>{a.displayName || a.username}</Text>
|
||||
<Text style={styles.rowName} numberOfLines={1}>{toUnicodeEmail(rowText.title)}</Text>
|
||||
{isActive && <Check size={14} color={c.success} />}
|
||||
</View>
|
||||
<Text style={styles.rowSub} numberOfLines={1}>{label}</Text>
|
||||
{rowText.subtitle ? <Text style={styles.rowSub} numberOfLines={1}>{toUnicodeEmail(rowText.subtitle)}</Text> : null}
|
||||
{isDefault && (
|
||||
<Text style={styles.defaultBadge}>
|
||||
{t('settings.account.accounts.default_badge', 'Default account')}
|
||||
@@ -226,7 +228,7 @@ export function AccountSettings() {
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={styles.iconBtn}
|
||||
onPress={() => confirmRemove(a.id, label)}
|
||||
onPress={() => confirmRemove(a.id, a.email || a.username)}
|
||||
disabled={busyId !== null}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t('common.remove', 'Remove')}
|
||||
|
||||
@@ -3,18 +3,13 @@ import { View, Text, StyleSheet } from 'react-native';
|
||||
import { SettingsSection, SettingItem, RadioGroup, ToggleSwitch } from './settings-section';
|
||||
import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens';
|
||||
import { useColors } from '../../theme/colors';
|
||||
import { useSettingsStore, type ThemeMode, type FontSize, type Density } from '../../stores/settings-store';
|
||||
import { useSettingsStore, type ThemeMode, type Density } from '../../stores/settings-store';
|
||||
import { useLocaleStore } from '../../stores/locale-store';
|
||||
import { setPendingSettingsTab } from '../../navigation/pending-settings-tab';
|
||||
import { densityPreviewConfig } from './density-preview';
|
||||
|
||||
type Theme = ThemeMode;
|
||||
|
||||
const DENSITY_PREVIEW: Record<Density, { py: number; gap: number; showAvatar: boolean; showPreview: boolean }> = {
|
||||
'extra-compact': { py: 2, gap: 6, showAvatar: false, showPreview: false },
|
||||
compact: { py: 4, gap: 8, showAvatar: true, showPreview: false },
|
||||
regular: { py: 10, gap: 12, showAvatar: true, showPreview: true },
|
||||
comfortable: { py: 16, gap: 16, showAvatar: true, showPreview: true },
|
||||
};
|
||||
|
||||
const PREVIEW_ROWS = [
|
||||
{ unread: true, sender: 'Alice Johnson', subject: 'Project update - Q1 roadmap', preview: 'Here are the latest numbers from…' },
|
||||
{ unread: false, sender: 'Bob Smith', subject: 'Re: Meeting notes', preview: 'Thanks, will review and get back...' },
|
||||
@@ -24,7 +19,8 @@ const PREVIEW_ROWS = [
|
||||
function DensityPreview({ density }: { density: Density }) {
|
||||
const c = useColors();
|
||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||
const cfg = DENSITY_PREVIEW[density];
|
||||
const showPreview = useSettingsStore((s) => s.showPreview);
|
||||
const cfg = densityPreviewConfig(density, showPreview);
|
||||
return (
|
||||
<View style={styles.densityPreview}>
|
||||
{PREVIEW_ROWS.map((row, i) => (
|
||||
@@ -85,14 +81,10 @@ export function AppearanceSettings() {
|
||||
const theme = useSettingsStore((s) => s.theme);
|
||||
const t = useLocaleStore((s) => s.t);
|
||||
const setTheme = useSettingsStore((s) => s.setTheme);
|
||||
const fontSize = useSettingsStore((s) => s.fontSize);
|
||||
const setFontSize = useSettingsStore((s) => s.setFontSize);
|
||||
const density = useSettingsStore((s) => s.density);
|
||||
const setDensity = useSettingsStore((s) => s.setDensity);
|
||||
const showToolbarLabels = useSettingsStore((s) => s.showToolbarLabels);
|
||||
const setShowToolbarLabels = useSettingsStore((s) => s.setShowToolbarLabels);
|
||||
const animationsEnabled = useSettingsStore((s) => s.animationsEnabled);
|
||||
const setAnimationsEnabled = useSettingsStore((s) => s.setAnimationsEnabled);
|
||||
const senderFavicons = useSettingsStore((s) => s.senderFavicons);
|
||||
const setSenderFavicons = useSettingsStore((s) => s.setSenderFavicons);
|
||||
const hydrated = useSettingsStore((s) => s.hydrated);
|
||||
@@ -119,17 +111,13 @@ export function AppearanceSettings() {
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t('settings.appearance.font_size.label', "Font Size")} description={t('settings.appearance.font_size.description', "Adjust text size for better readability")}>
|
||||
<RadioGroup
|
||||
value={fontSize}
|
||||
onChange={(v) => setFontSize(v as FontSize)}
|
||||
options={[
|
||||
{ value: 'small', label: t('settings.appearance.font_size.small', "Small") },
|
||||
{ value: 'medium', label: t('settings.appearance.font_size.medium', "Medium") },
|
||||
{ value: 'large', label: t('settings.appearance.font_size.large', "Large") },
|
||||
]}
|
||||
/>
|
||||
</SettingItem>
|
||||
{/* Text size and motion moved to Settings > Accessibility. */}
|
||||
<SettingItem
|
||||
label={t('settings.appearance.accessibility_link.label', 'Text size and motion')}
|
||||
description={t('settings.appearance.accessibility_link.description_mobile', 'Text size, reduced motion and the accessibility presets are on the Accessibility page.')}
|
||||
onPress={() => setPendingSettingsTab('accessibility')}
|
||||
link="page"
|
||||
/>
|
||||
|
||||
<View>
|
||||
<SettingItem label={t('settings.appearance.list_density.label', "Density")} description={t('settings.appearance.list_density.description', "Control spacing and padding across the UI")} noBorder />
|
||||
@@ -151,10 +139,6 @@ export function AppearanceSettings() {
|
||||
<ToggleSwitch checked={showToolbarLabels} onChange={setShowToolbarLabels} />
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t('settings.appearance.animations.label', "Enable Animations")} description={t('settings.appearance.animations.description', "Show smooth transitions and effects")}>
|
||||
<ToggleSwitch checked={animationsEnabled} onChange={setAnimationsEnabled} />
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem
|
||||
label={t('settings.advanced.sender_favicons.label', 'Sender Favicons')}
|
||||
description={t('settings.advanced.sender_favicons.description', 'Show website icons as profile pictures for business senders')}
|
||||
|
||||
@@ -226,7 +226,7 @@ export function ComposingSettings() {
|
||||
</View>
|
||||
|
||||
<View style={styles.addRow}>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={t('settings.email_behavior.attachment_reminder.add_placeholder', "Add keyword...")}
|
||||
value={newKeyword}
|
||||
onChangeText={setNewKeyword}
|
||||
placeholder={t('settings.email_behavior.attachment_reminder.add_placeholder', "Add keyword...")}
|
||||
|
||||
@@ -224,6 +224,7 @@ export function ContactsSettings() {
|
||||
{editingId === book.id ? (
|
||||
<>
|
||||
<TextInput
|
||||
accessibilityLabel={t('contacts.address_books.name_label', 'Address book name')}
|
||||
style={styles.bookInput}
|
||||
value={editName}
|
||||
onChangeText={setEditName}
|
||||
@@ -311,7 +312,7 @@ export function ContactsSettings() {
|
||||
{adding ? (
|
||||
<View style={styles.bookRow}>
|
||||
<BookUser size={16} color={c.textSecondary} />
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={t('contacts.address_books.name_label', 'Address book name')}
|
||||
style={styles.bookInput}
|
||||
value={newName}
|
||||
onChangeText={setNewName}
|
||||
|
||||
@@ -211,7 +211,7 @@ export function ContentSendersSettings() {
|
||||
</View>
|
||||
|
||||
<View style={styles.addRow}>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={t('settings.email_behavior.trusted_senders.add_placeholder', 'Enter email address')}
|
||||
value={newSender}
|
||||
onChangeText={setNewSender}
|
||||
placeholder={t('settings.email_behavior.trusted_senders.add_placeholder', 'Enter email address')}
|
||||
|
||||
@@ -52,6 +52,7 @@ export function DownloadsSettings() {
|
||||
description={t('settings.downloads.email_template.description_mobile', "Template used when exporting a message as an .eml file.")}
|
||||
>
|
||||
<Input
|
||||
accessibilityLabel={t('settings.downloads.email_template.label', "Email (.eml) filename")}
|
||||
value={emailTemplate}
|
||||
onChangeText={(v) => update('emailExportTemplate', v)}
|
||||
autoCapitalize="none"
|
||||
@@ -76,6 +77,7 @@ export function DownloadsSettings() {
|
||||
description={t('settings.downloads.attachment_template.description_mobile', "Template used when saving or sharing an attachment.")}
|
||||
>
|
||||
<Input
|
||||
accessibilityLabel={t('settings.downloads.attachment_template.label', "Attachment filename")}
|
||||
value={attachmentTemplate}
|
||||
onChangeText={(v) => update('attachmentExportTemplate', v)}
|
||||
autoCapitalize="none"
|
||||
|
||||
@@ -318,7 +318,7 @@ export function FolderSettings() {
|
||||
</View>
|
||||
<ScrollView contentContainerStyle={styles.modalBody} keyboardShouldPersistTaps="handled">
|
||||
<Text style={styles.fieldLabel}>{t('settings.folders.folder_name', 'Folder name')}</Text>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={t('settings.folders.folder_name', 'Folder name')}
|
||||
value={draftName}
|
||||
onChangeText={setDraftName}
|
||||
placeholder={t('settings.folders.name_placeholder', 'Receipts')}
|
||||
|
||||
@@ -30,6 +30,7 @@ import { sanitizeSignatureHtml } from '../../lib/signature-utils';
|
||||
import { MODAL_ORIENTATIONS } from '../../lib/modal-orientations';
|
||||
import { useSheetInsets } from '../../lib/use-layout';
|
||||
import Badge from '../Badge';
|
||||
import { identityDraftChanged } from './identity-draft';
|
||||
|
||||
type DraftIdentity = {
|
||||
id: string;
|
||||
@@ -117,9 +118,35 @@ export function IdentitySettings() {
|
||||
void fetchIdentities();
|
||||
}, [fetchIdentities]);
|
||||
|
||||
const openCreate = () => setEditing(emptyDraft());
|
||||
const openEdit = (i: Identity) => setEditing(toDraft(i));
|
||||
const closeEditor = () => setEditing(null);
|
||||
// The draft as it was opened, to tell whether closing would drop changes.
|
||||
const [initialDraft, setInitialDraft] = useState<DraftIdentity | null>(null);
|
||||
const openDraft = (draft: DraftIdentity) => {
|
||||
setEditing(draft);
|
||||
setInitialDraft(draft);
|
||||
};
|
||||
const openCreate = () => openDraft(emptyDraft());
|
||||
const openEdit = (i: Identity) => openDraft(toDraft(i));
|
||||
const closeEditor = () => {
|
||||
setEditing(null);
|
||||
setInitialDraft(null);
|
||||
};
|
||||
// Close, Cancel and Android Back ask first when something was typed.
|
||||
const requestClose = () => {
|
||||
if (saving) return;
|
||||
if (!identityDraftChanged(initialDraft, editing)) {
|
||||
closeEditor();
|
||||
return;
|
||||
}
|
||||
Alert.alert(
|
||||
t('settings.discard_changes', 'Discard changes?'),
|
||||
undefined,
|
||||
[
|
||||
{ text: t('settings.keep_editing', 'Keep editing'), style: 'cancel' },
|
||||
{ text: t('settings.discard', 'Discard'), style: 'destructive', onPress: closeEditor },
|
||||
],
|
||||
{ cancelable: true },
|
||||
);
|
||||
};
|
||||
|
||||
const setAsDefault = (identity: Identity) => {
|
||||
if (!accountId) return;
|
||||
@@ -301,20 +328,20 @@ export function IdentitySettings() {
|
||||
</Pressable>
|
||||
))}
|
||||
|
||||
<Modal supportedOrientations={MODAL_ORIENTATIONS} visible={!!editing} animationType="slide" transparent onRequestClose={closeEditor}>
|
||||
<Modal supportedOrientations={MODAL_ORIENTATIONS} visible={!!editing} animationType="slide" transparent onRequestClose={requestClose}>
|
||||
<View style={[styles.modalOverlay, { paddingLeft: sheetInsets.left, paddingRight: sheetInsets.right }]}>
|
||||
<View style={styles.modalSheet}>
|
||||
<View style={styles.modalHeader}>
|
||||
<Text style={styles.modalTitle}>
|
||||
{editing?.id ? t('settings.identities.edit', "Edit identity") : t('settings.identities.create', "New identity")}
|
||||
</Text>
|
||||
<Pressable onPress={closeEditor} hitSlop={8} accessibilityRole="button" accessibilityLabel={t('common.close', "Close")}>
|
||||
<Pressable onPress={requestClose} hitSlop={8} accessibilityRole="button" accessibilityLabel={t('common.close', "Close")}>
|
||||
<X size={20} color={c.text} />
|
||||
</Pressable>
|
||||
</View>
|
||||
<ScrollView contentContainerStyle={styles.modalBody} keyboardShouldPersistTaps="handled">
|
||||
<Text style={styles.fieldLabel}>{t('settings.identities.display_name', "Display name")}</Text>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={t('settings.identities.display_name', "Display name")}
|
||||
value={editing?.name ?? ''}
|
||||
onChangeText={(name) => setEditing((d) => (d ? { ...d, name } : d))}
|
||||
placeholder={t('settings.identities.display_name_placeholder', 'Jane Doe')}
|
||||
@@ -322,7 +349,7 @@ export function IdentitySettings() {
|
||||
style={styles.input}
|
||||
/>
|
||||
<Text style={styles.fieldLabel}>{t('settings.identities.email_address', "Email address")}</Text>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={t('settings.identities.email_address', "Email address")}
|
||||
value={editing?.email ?? ''}
|
||||
onChangeText={(email) => setEditing((d) => (d ? { ...d, email } : d))}
|
||||
placeholder={t('identities.form.email_placeholder', 'your.email@example.com')}
|
||||
@@ -337,7 +364,7 @@ export function IdentitySettings() {
|
||||
<Text style={styles.hint}>{t('settings.identities.email_locked', "JMAP does not allow changing an identity's email; create a new one instead.")}</Text>
|
||||
)}
|
||||
<Text style={styles.fieldLabel}>{t('identities.form.reply_to_label', 'Reply-To (optional)')}</Text>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={t('identities.form.reply_to_label', 'Reply-To (optional)')}
|
||||
value={editing?.replyTo ?? ''}
|
||||
onChangeText={(replyTo) => setEditing((d) => (d ? { ...d, replyTo } : d))}
|
||||
placeholder={t('identities.form.reply_to_placeholder', 'different@example.com')}
|
||||
@@ -348,7 +375,7 @@ export function IdentitySettings() {
|
||||
keyboardType="email-address"
|
||||
/>
|
||||
<Text style={styles.fieldLabel}>{t('identities.form.bcc_label', 'Auto BCC (optional)')}</Text>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={t('identities.form.bcc_label', 'Auto BCC (optional)')}
|
||||
value={editing?.bcc ?? ''}
|
||||
onChangeText={(bcc) => setEditing((d) => (d ? { ...d, bcc } : d))}
|
||||
placeholder={t('identities.form.bcc_placeholder', 'archive@example.com')}
|
||||
@@ -359,7 +386,7 @@ export function IdentitySettings() {
|
||||
keyboardType="email-address"
|
||||
/>
|
||||
<Text style={styles.fieldLabel}>{t('identities.form.text_signature_label', "Text Signature")}</Text>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={t('identities.form.text_signature_label', "Text Signature")}
|
||||
value={editing?.textSignature ?? ''}
|
||||
onChangeText={(textSignature) => setEditing((d) => (d ? { ...d, textSignature } : d))}
|
||||
placeholder="-- Jane Doe Bulwark Mail"
|
||||
@@ -371,7 +398,7 @@ export function IdentitySettings() {
|
||||
{t('identities.form.signature_byte_counter', '{bytes} / {max} bytes', { bytes: textBytes, max: SIGNATURE_MAX_BYTES })}
|
||||
</Text>
|
||||
<Text style={styles.fieldLabel}>{t('identities.form.html_signature_label', "HTML Signature")}</Text>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={t('identities.form.html_signature_label', "HTML Signature")}
|
||||
value={editing?.htmlSignature ?? ''}
|
||||
onChangeText={(htmlSignature) => setEditing((d) => (d ? { ...d, htmlSignature } : d))}
|
||||
placeholder="<p><b>Jane Doe</b><br>Bulwark Mail</p>"
|
||||
@@ -386,7 +413,7 @@ export function IdentitySettings() {
|
||||
</Text>
|
||||
</ScrollView>
|
||||
<View style={styles.modalActions}>
|
||||
<Button variant="outline" size="sm" onPress={closeEditor} disabled={saving}>{t('common.cancel', "Cancel")}</Button>
|
||||
<Button variant="outline" size="sm" onPress={requestClose} disabled={saving}>{t('common.cancel', "Cancel")}</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
|
||||
@@ -304,7 +304,7 @@ function KeywordForm({ initial, existingIds, onSave, onCancel }: KeywordFormProp
|
||||
<View style={styles.form}>
|
||||
<View>
|
||||
<Text style={styles.formLabel}>{t('settings.keywords.label_field', "Display Name")}</Text>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={t('settings.keywords.label_field', "Display Name")}
|
||||
value={label}
|
||||
onChangeText={setLabel}
|
||||
placeholder={t('settings.keywords.label_placeholder', "e.g. Work, Personal, Urgent")}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
AppState,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
@@ -22,7 +23,8 @@ import {
|
||||
DEFAULT_RELAY_BASE_URL,
|
||||
disablePushForAccount,
|
||||
getStoredRelayBaseUrl,
|
||||
isPushEnabledForAccount,
|
||||
isPushActiveForAccount,
|
||||
shouldRecheckPushState,
|
||||
isPushSupported,
|
||||
isValidRelayUrl,
|
||||
listPushDevices,
|
||||
@@ -94,11 +96,14 @@ export function NotificationSettings() {
|
||||
try { ownJmapAccountId = jmapClient.accountId; } catch { ownJmapAccountId = null; }
|
||||
const canMuteAccount = ownJmapAccountId !== null && serverSupportsEmailPush();
|
||||
const accountMuted = ownJmapAccountId !== null && isAccountMuted(notificationMutes, ownJmapAccountId);
|
||||
// The Inbox-only filter needs the server's emailPush filter too (webmail #983).
|
||||
const inboxOnly = useSettingsStore((s) => s.pushNotifyInboxOnly);
|
||||
const calEnabled = useSettingsStore((s) => s.calendarNotificationsEnabled);
|
||||
const invitationParsing = useSettingsStore((s) => s.calendarInvitationParsingEnabled);
|
||||
const appIconBadge = useSettingsStore((s) => s.appIconUnreadBadge);
|
||||
|
||||
const supported = isPushSupported();
|
||||
const inboxOnlyAvailable = supported && !!client && serverSupportsEmailPush();
|
||||
const upSupported = isUnifiedPushSupported();
|
||||
const [relayUrl, setRelayUrl] = useState(DEFAULT_RELAY_BASE_URL);
|
||||
const [transport, setTransport] = useState<PushTransport>('fcm');
|
||||
@@ -115,11 +120,25 @@ export function NotificationSettings() {
|
||||
if (!hydrated) void hydrate();
|
||||
}, [hydrated, hydrate]);
|
||||
|
||||
// Read again when the app comes back to the front: notifications may have
|
||||
// been turned off in the phone's settings meanwhile (webmail #1148).
|
||||
const [recheck, setRecheck] = useState(0);
|
||||
const pushStatusKind = React.useRef(pushStatus.kind);
|
||||
pushStatusKind.current = pushStatus.kind;
|
||||
useEffect(() => {
|
||||
const sub = AppState.addEventListener('change', (state) => {
|
||||
if (state === 'active' && shouldRecheckPushState(pushStatusKind.current)) setRecheck((n) => n + 1);
|
||||
});
|
||||
return () => sub.remove();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
void (async () => {
|
||||
const stored = await getStoredRelayBaseUrl();
|
||||
const enabled = activeAccountId ? await isPushEnabledForAccount(activeAccountId) : false;
|
||||
const enabled = activeAccountId ? await isPushActiveForAccount(activeAccountId) : false;
|
||||
// An enable or disable that started meanwhile has the final word.
|
||||
if (!shouldRecheckPushState(pushStatusKind.current)) return;
|
||||
if (cancelled) return;
|
||||
if (stored) setRelayUrl(stored);
|
||||
setPushEnabled(enabled);
|
||||
@@ -134,7 +153,7 @@ export function NotificationSettings() {
|
||||
})();
|
||||
return () => { cancelled = true; };
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeAccountId]);
|
||||
}, [activeAccountId, recheck]);
|
||||
|
||||
const trimmed = relayUrl.trim().replace(/\/+$/, '');
|
||||
const relayValid = isValidRelayUrl(trimmed);
|
||||
@@ -557,6 +576,18 @@ export function NotificationSettings() {
|
||||
>
|
||||
<ToggleSwitch checked={emailEnabled} onChange={(v) => update('emailNotificationsEnabled', v)} />
|
||||
</SettingItem>
|
||||
{inboxOnlyAvailable && (
|
||||
<SettingItem
|
||||
label={t('settings.notifications.email.inbox_only', 'Inbox only')}
|
||||
description={t('settings.notifications.email.inbox_only_desc', 'Only notify for mail that lands in the Inbox; skip mail a filter files into another folder')}
|
||||
>
|
||||
<ToggleSwitch
|
||||
checked={inboxOnly}
|
||||
onChange={(v) => update('pushNotifyInboxOnly', v)}
|
||||
accessibilityLabel={t('settings.notifications.email.inbox_only', 'Inbox only')}
|
||||
/>
|
||||
</SettingItem>
|
||||
)}
|
||||
{canMuteAccount && (
|
||||
<SettingItem
|
||||
label={t('settings.notifications.email.mute_account', 'Mute this account')}
|
||||
|
||||
@@ -246,7 +246,7 @@ export function ReadingSettings() {
|
||||
))}
|
||||
</View>
|
||||
<View style={styles.addKeywordRow}>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={t('settings.email_behavior.attachment_reminder.add_placeholder', "Add keyword...")}
|
||||
value={newKeyword}
|
||||
onChangeText={setNewKeyword}
|
||||
placeholder={t('settings.email_behavior.attachment_reminder.add_placeholder', "Add keyword...")}
|
||||
|
||||
@@ -133,7 +133,7 @@ function AppForm({ initial, onSave, onCancel }: AppFormProps) {
|
||||
<View style={formStyles.form}>
|
||||
<View>
|
||||
<Text style={formStyles.label}>{t('settings.sidebar_apps.form.name', "Name")}</Text>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={t('settings.sidebar_apps.form.name', "Name")}
|
||||
value={name}
|
||||
onChangeText={setName}
|
||||
placeholder={t('settings.sidebar_apps.form.name_placeholder', "App name")}
|
||||
@@ -144,7 +144,7 @@ function AppForm({ initial, onSave, onCancel }: AppFormProps) {
|
||||
|
||||
<View>
|
||||
<Text style={formStyles.label}>{t('settings.sidebar_apps.form.url', "URL")}</Text>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={t('settings.sidebar_apps.form.url', "URL")}
|
||||
value={url}
|
||||
onChangeText={setUrl}
|
||||
placeholder="https://example.com"
|
||||
|
||||
@@ -224,7 +224,7 @@ export function TemplateSettings() {
|
||||
</View>
|
||||
<ScrollView contentContainerStyle={styles.modalBody}>
|
||||
<Text style={styles.fieldLabel}>{tr('settings.templates.name', 'Template Name')}</Text>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={tr('settings.templates.name', 'Template Name')}
|
||||
value={draftName}
|
||||
onChangeText={setDraftName}
|
||||
placeholder={tr('settings.templates.name_placeholder', 'e.g., Follow-up email')}
|
||||
@@ -232,7 +232,7 @@ export function TemplateSettings() {
|
||||
style={styles.input}
|
||||
/>
|
||||
<Text style={styles.fieldLabel}>{tr('settings.templates.subject', 'Subject')}</Text>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={tr('settings.templates.subject', 'Subject')}
|
||||
value={draftSubject}
|
||||
onChangeText={setDraftSubject}
|
||||
placeholder={tr('settings.templates.subject_placeholder', 'Email subject line')}
|
||||
@@ -240,7 +240,7 @@ export function TemplateSettings() {
|
||||
style={styles.input}
|
||||
/>
|
||||
<Text style={styles.fieldLabel}>{tr('settings.templates.category', 'Category')}</Text>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={tr('settings.templates.category', 'Category')}
|
||||
value={draftCategory}
|
||||
onChangeText={setDraftCategory}
|
||||
placeholder={tr('settings.templates.category_placeholder', 'e.g., Work, Personal')}
|
||||
@@ -248,7 +248,7 @@ export function TemplateSettings() {
|
||||
style={styles.input}
|
||||
/>
|
||||
<Text style={styles.fieldLabel}>{tr('settings.templates.body', 'Body')}</Text>
|
||||
<TextInput
|
||||
<TextInput accessibilityLabel={tr('settings.templates.body', 'Body')}
|
||||
value={draftBody}
|
||||
onChangeText={setDraftBody}
|
||||
placeholder={tr('settings.templates.body_placeholder', 'Email body content...')}
|
||||
@@ -297,6 +297,7 @@ export function TemplateSettings() {
|
||||
<ScrollView contentContainerStyle={styles.modalBody}>
|
||||
<Text style={styles.fieldLabel}>JSON</Text>
|
||||
<TextInput
|
||||
accessibilityLabel={tr('settings.templates.import_json_label', 'Templates as JSON')}
|
||||
value={importText}
|
||||
onChangeText={setImportText}
|
||||
placeholder='{"templates": [...]}'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user