mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-01 16:05:39 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53c0bc0fc2 | ||
|
|
a8e74c8b6d | ||
|
|
949bb10ac5 | ||
|
|
e5659ed5e8 | ||
|
|
a764a07c65 | ||
|
|
5ddc2df64b | ||
|
|
fef23e4c33 | ||
|
|
4a3bce154c | ||
|
|
13a3e95a0a | ||
|
|
fd55e16492 | ||
|
|
5e557266d1 | ||
|
|
b178ee8d07 | ||
|
|
1cbf763a36 | ||
|
|
84fef142b9 | ||
|
|
b3a82f1241 | ||
|
|
13cba64854 | ||
|
|
e3501f939d | ||
|
|
5e3cbf2419 | ||
|
|
9c031c8761 | ||
|
|
c81dc5179f | ||
|
|
d72aab2519 | ||
|
|
d4ac1e1df4 | ||
|
|
06ce86e31a | ||
|
|
6fe907b4bc | ||
|
|
c1e5cd6c8b | ||
|
|
455608f9f0 | ||
|
|
5801d6c97a | ||
|
|
771558d8c7 | ||
|
|
9c0c35c7ba | ||
|
|
ccdc4dee56 | ||
|
|
aab29da148 | ||
|
|
d59c571d93 | ||
|
|
cb5c152cb3 | ||
|
|
6e6fd95e5e | ||
|
|
8af163a6a3 | ||
|
|
b7f711050e | ||
|
|
9282ee6d72 | ||
|
|
a8e6f6e8c4 | ||
|
|
b7a5953255 | ||
|
|
0cee422373 | ||
|
|
644443193c | ||
|
|
d2066dad07 | ||
|
|
dd0969d48b | ||
|
|
96bffa51e8 | ||
|
|
5434656e03 | ||
|
|
de3d697aa9 | ||
|
|
caf61ece79 | ||
|
|
71522b9b92 | ||
|
|
67935d2edc | ||
|
|
d7c890869b | ||
|
|
4f3940f1d3 | ||
|
|
9c2f819eac | ||
|
|
9fa931c589 | ||
|
|
1196ad56c2 | ||
|
|
3bf2c30c99 | ||
|
|
018accd865 | ||
|
|
1c7c2a3be8 | ||
|
|
49ed48791d | ||
|
|
ef143a4dbb | ||
|
|
2a055ddfe2 | ||
|
|
cd911ada79 | ||
|
|
e0b72883ac | ||
|
|
54b169c6c7 | ||
|
|
6ebadf738f | ||
|
|
01fc6fbba5 | ||
|
|
7cdebc465a | ||
|
|
ad1f053f72 | ||
|
|
1348e7d20a | ||
|
|
ac8e3fb2e9 | ||
|
|
33e303e248 | ||
|
|
96c5a3f046 | ||
|
|
f397ee3e1b | ||
|
|
84548898c1 | ||
|
|
1a651a9c25 | ||
|
|
83db70ac86 | ||
|
|
a4ac6cad5c | ||
|
|
a7e4e84f5d | ||
|
|
e0a79ca5c6 | ||
|
|
edab181e60 | ||
|
|
d097ae9ec3 | ||
|
|
8d5bced7f3 | ||
|
|
ee50418bac | ||
|
|
ddff2f7b66 | ||
|
|
d8db4a5597 | ||
|
|
1fb60a5c61 | ||
|
|
115e162b86 | ||
|
|
a3193ebaf7 | ||
|
|
40ace883b7 | ||
|
|
9135deed80 | ||
|
|
3c7b96e11a | ||
|
|
2e78c9870c | ||
|
|
46e285b3c9 | ||
|
|
4c9efbc44d | ||
|
|
7964e646c4 | ||
|
|
008bff29f8 | ||
|
|
af6290fd4c | ||
|
|
4e6426171a | ||
|
|
b1fb2e9b22 | ||
|
|
25c088383f | ||
|
|
9f75e70218 | ||
|
|
30aea63a66 | ||
|
|
85f23dfdf4 | ||
|
|
4a5d837cd7 | ||
|
|
f2dd3a8b4b | ||
|
|
b9db3764ff | ||
|
|
82ca359df7 | ||
|
|
5bcfe1fe4d | ||
|
|
063432bf28 | ||
|
|
604eb39340 | ||
|
|
6c31c8d0c6 | ||
|
|
3f88868aad | ||
|
|
d53bfec68a | ||
|
|
4fdf39aa08 | ||
|
|
409cdb139e | ||
|
|
1d755fc066 | ||
|
|
a6ef0abc2a | ||
|
|
c0582ab950 | ||
|
|
15717f137a | ||
|
|
4b903ed179 | ||
|
|
6a975b822c | ||
|
|
1b64c14280 | ||
|
|
dcdda5ed9f | ||
|
|
548b647f15 | ||
|
|
fc00781b47 | ||
|
|
3faac98627 | ||
|
|
472c4d8f26 | ||
|
|
0ffdcacea9 | ||
|
|
2d907ffab8 | ||
|
|
a0c2bd7f4a | ||
|
|
235f4ea77f | ||
|
|
92b42e4a4f | ||
|
|
191f4f32cd | ||
|
|
7e598c5bc9 | ||
|
|
ebd8dacdfd | ||
|
|
aa250759e5 | ||
|
|
cb9a741244 | ||
|
|
695aa23563 | ||
|
|
8618abf1d7 | ||
|
|
a3d3af797c | ||
|
|
ddfe85a5b2 | ||
|
|
ccaa24ecd3 | ||
|
|
d8046a8aad | ||
|
|
9e95273c14 | ||
|
|
33d26744b6 | ||
|
|
5e42c6737e | ||
|
|
9c31de2e80 | ||
|
|
5c7301b3f1 | ||
|
|
d987930034 | ||
|
|
35e0792558 | ||
|
|
0a8686a008 | ||
|
|
668e01023c | ||
|
|
c888e95c5a | ||
|
|
e6be8922b7 | ||
|
|
182774884e | ||
|
|
a734d0ade1 | ||
|
|
95c24a4214 | ||
|
|
eca3871545 | ||
|
|
d89e33110a | ||
|
|
ac58b36a77 | ||
|
|
e6549d31da | ||
|
|
2511df1059 | ||
|
|
a86cd1d0eb | ||
|
|
f63d739fb3 | ||
|
|
72f48db4e5 | ||
|
|
4e7ae2fa30 | ||
|
|
48825e919a | ||
|
|
24849baca5 | ||
|
|
e37621be5e | ||
|
|
29169c3426 | ||
|
|
5237848312 | ||
|
|
746572c784 | ||
|
|
6f0abb4384 | ||
|
|
1d1d6a5bfc | ||
|
|
d45c7996d2 | ||
|
|
476780553c | ||
|
|
910c503f4a | ||
|
|
7531118727 | ||
|
|
55f1ec431f | ||
|
|
cb2f03bc09 | ||
|
|
50418971f4 | ||
|
|
859770b814 | ||
|
|
c51a848dfb | ||
|
|
e67947aa93 | ||
|
|
802b19916c | ||
|
|
e28e8b4d9c | ||
|
|
ee652d39df | ||
|
|
06e89422bd | ||
|
|
cd4ce9d08b | ||
|
|
8d7fbfc99c | ||
|
|
caea3d0c60 | ||
|
|
00ad2f49fb | ||
|
|
4eb8c47670 | ||
|
|
bd42206686 | ||
|
|
ab845783ab | ||
|
|
5f3f584ca8 | ||
|
|
cb6ddd6fcf | ||
|
|
ee04e95d4f | ||
|
|
5b8c4353d8 | ||
|
|
c2584c2bd5 | ||
|
|
6c82130267 | ||
|
|
f228257ec9 | ||
|
|
a02e7f8a49 | ||
|
|
ea1f53f550 | ||
|
|
4e035f3011 | ||
|
|
e04bea451a |
@@ -10,6 +10,13 @@ on:
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
# Signing is described in docs/android-release.md.
|
||||
env:
|
||||
# Certificate SHA-256 of android/app/debug.keystore, the stock React Native
|
||||
# template key (CN=Android Debug, SHA-1 5E:8F:16:06:...:F6:25). It is public,
|
||||
# so Android 9+ must never see it as the signer of a published build.
|
||||
DEBUG_CERT_SHA256: fac61745dc0903786fb9ede62a962b399f7348f0bb6f899b8332667591033b9c
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -17,6 +24,26 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
# Fail in seconds instead of after a 20-minute build. There is no
|
||||
# unsigned or debug-signed fallback: anything signed with the debug key
|
||||
# can be "updated" by anyone.
|
||||
- name: Require the release signing secrets
|
||||
env:
|
||||
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||
KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
run: |
|
||||
missing=""
|
||||
[ -n "$KEYSTORE_BASE64" ] || missing="$missing ANDROID_KEYSTORE_BASE64"
|
||||
[ -n "$KEYSTORE_PASSWORD" ] || missing="$missing ANDROID_KEYSTORE_PASSWORD"
|
||||
[ -n "$KEY_ALIAS" ] || missing="$missing ANDROID_KEY_ALIAS"
|
||||
[ -n "$KEY_PASSWORD" ] || missing="$missing ANDROID_KEY_PASSWORD"
|
||||
if [ -n "$missing" ]; then
|
||||
echo "::error title=Release signing is not configured::Missing repository secrets:$missing. Release APKs are never signed with the public debug key; see docs/android-release.md."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
@@ -66,23 +93,38 @@ jobs:
|
||||
- name: Install JS dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Decode release keystore
|
||||
id: keystore
|
||||
# Checks the password and alias before the long build, and refuses a
|
||||
# debug key that was uploaded as the release key by mistake.
|
||||
- name: Decode and check the release key
|
||||
id: key
|
||||
env:
|
||||
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||
KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
run: |
|
||||
if [ -n "$KEYSTORE_BASE64" ]; then
|
||||
echo "$KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/release.keystore"
|
||||
echo "path=$RUNNER_TEMP/release.keystore" >> "$GITHUB_OUTPUT"
|
||||
echo "present=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "present=false" >> "$GITHUB_OUTPUT"
|
||||
set -euo pipefail
|
||||
KEYSTORE="$RUNNER_TEMP/release.keystore"
|
||||
CERT="$RUNNER_TEMP/release-cert.der"
|
||||
echo "$KEYSTORE_BASE64" | base64 -d > "$KEYSTORE"
|
||||
if ! keytool -exportcert -keystore "$KEYSTORE" -alias "$KEY_ALIAS" \
|
||||
-storepass:env KEYSTORE_PASSWORD -file "$CERT"; then
|
||||
echo "::error title=Release key unreadable::Could not read alias ANDROID_KEY_ALIAS from ANDROID_KEYSTORE_BASE64 with ANDROID_KEYSTORE_PASSWORD."
|
||||
exit 1
|
||||
fi
|
||||
CERT_SHA256=$(sha256sum "$CERT" | cut -d' ' -f1)
|
||||
OWNER=$(keytool -printcert -file "$CERT")
|
||||
if [ "$CERT_SHA256" = "$DEBUG_CERT_SHA256" ] || [[ "$OWNER" == *"CN=Android Debug"* ]]; then
|
||||
echo "::error title=Release key is a debug key::ANDROID_KEYSTORE_BASE64 holds an Android debug key. Generate a release key as described in docs/android-release.md."
|
||||
exit 1
|
||||
fi
|
||||
echo "Release certificate SHA-256: $CERT_SHA256"
|
||||
echo "store_file=$KEYSTORE" >> "$GITHUB_OUTPUT"
|
||||
echo "cert_sha256=$CERT_SHA256" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build release APK
|
||||
working-directory: android
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_BULWARK_RELEASE_STORE_FILE: ${{ steps.keystore.outputs.path }}
|
||||
ORG_GRADLE_PROJECT_BULWARK_RELEASE_STORE_FILE: ${{ steps.key.outputs.store_file }}
|
||||
ORG_GRADLE_PROJECT_BULWARK_RELEASE_STORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
ORG_GRADLE_PROJECT_BULWARK_RELEASE_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
ORG_GRADLE_PROJECT_BULWARK_RELEASE_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
@@ -90,15 +132,135 @@ jobs:
|
||||
chmod +x ./gradlew
|
||||
./gradlew assembleRelease --no-daemon
|
||||
|
||||
- name: Rename APK with version
|
||||
- name: Put apksigner on the PATH
|
||||
run: |
|
||||
BUILD_TOOLS=$(ls -d "$ANDROID_HOME"/build-tools/*/ | sort -V | tail -n 1)
|
||||
echo "Using $BUILD_TOOLS"
|
||||
echo "$BUILD_TOOLS" >> "$GITHUB_PATH"
|
||||
|
||||
# Optional v3 key rotation away from the debug key that signed every
|
||||
# release up to 0.1.62. Gradle signed the APK with the release key only,
|
||||
# which existing installs reject as a different app. With a lineage the
|
||||
# APK is re-signed so that Android 9+ sees the release key plus proof
|
||||
# that the debug key handed over to it, while Android 7-8.1 (v2 only)
|
||||
# still see the debug key. docs/android-release.md has the details.
|
||||
- name: Apply the signing key rotation
|
||||
id: rotation
|
||||
env:
|
||||
LINEAGE_BASE64: ${{ secrets.ANDROID_SIGNING_LINEAGE_BASE64 }}
|
||||
KEYSTORE: ${{ steps.key.outputs.store_file }}
|
||||
KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
RELEASE_CERT_SHA256: ${{ steps.key.outputs.cert_sha256 }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "$LINEAGE_BASE64" ]; then
|
||||
echo "No ANDROID_SIGNING_LINEAGE_BASE64: keeping the release-key-only signature."
|
||||
echo "rotated=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
LINEAGE="$RUNNER_TEMP/signing.lineage"
|
||||
echo "$LINEAGE_BASE64" | base64 -d > "$LINEAGE"
|
||||
apksigner lineage --in "$LINEAGE" --print-certs | tee "$RUNNER_TEMP/lineage.txt"
|
||||
|
||||
DIGESTS=$(sed -n 's/^Signer #[0-9]* in lineage certificate SHA-256 digest: //p' "$RUNNER_TEMP/lineage.txt")
|
||||
if [ "$(sed -n '1p' <<< "$DIGESTS")" != "$DEBUG_CERT_SHA256" ] \
|
||||
|| [ "$(sed -n '$p' <<< "$DIGESTS")" != "$RELEASE_CERT_SHA256" ]; then
|
||||
echo "::error title=Wrong signing lineage::ANDROID_SIGNING_LINEAGE_BASE64 must lead from the debug key to the key in ANDROID_KEYSTORE_BASE64."
|
||||
exit 1
|
||||
fi
|
||||
# Rollback on an old signer lets anything signed with it take the app
|
||||
# back after the rotation, which would undo the whole point.
|
||||
PAST_ROLLBACK=$(grep 'Has rollback capability' "$RUNNER_TEMP/lineage.txt" | sed -e 's/.*: *//' -e '$d')
|
||||
if [[ "$PAST_ROLLBACK" == *true* ]]; then
|
||||
echo "::error title=Unsafe signing lineage::An old signer in the lineage has the rollback capability. Recreate it with --set-rollback false."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Oldest signer first: apksigner uses it for v2, and the rotated key
|
||||
# for v3 from API 28. Without --rotation-min-sdk-version it would
|
||||
# only rotate on Android 13+ (v3.1) and leave 9-12L on the debug key.
|
||||
APK=android/app/build/outputs/apk/release/app-release.apk
|
||||
apksigner sign \
|
||||
--v4-signing-enabled false \
|
||||
--ks android/app/debug.keystore --ks-key-alias androiddebugkey \
|
||||
--ks-pass pass:android --key-pass pass:android \
|
||||
--next-signer \
|
||||
--ks "$KEYSTORE" --ks-key-alias "$KEY_ALIAS" \
|
||||
--ks-pass env:KEYSTORE_PASSWORD --key-pass env:KEY_PASSWORD \
|
||||
--lineage "$LINEAGE" \
|
||||
--rotation-min-sdk-version 28 \
|
||||
--out "$RUNNER_TEMP/app-release-rotated.apk" \
|
||||
"$APK"
|
||||
mv "$RUNNER_TEMP/app-release-rotated.apk" "$APK"
|
||||
echo "rotated=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# The last gate before anything is uploaded.
|
||||
- name: Verify the APK signature
|
||||
env:
|
||||
RELEASE_CERT_SHA256: ${{ steps.key.outputs.cert_sha256 }}
|
||||
ROTATED: ${{ steps.rotation.outputs.rotated }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
APK=android/app/build/outputs/apk/release/app-release.apk
|
||||
apksigner verify --verbose --print-certs "$APK"
|
||||
signers() {
|
||||
apksigner verify --print-certs "$@" "$APK" \
|
||||
| sed -n 's/^Signer #[0-9]* certificate SHA-256 digest: //p'
|
||||
}
|
||||
MODERN=$(signers --min-sdk-version 28)
|
||||
LEGACY=$(signers --max-sdk-version 27)
|
||||
echo "Signer on Android 9+: $MODERN"
|
||||
echo "Signer on Android 7-8.1: $LEGACY"
|
||||
|
||||
if [[ "$MODERN" == *"$DEBUG_CERT_SHA256"* ]]; then
|
||||
echo "::error title=APK is debug-signed::Android 9+ would see the public debug key as the signer."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$MODERN" != "$RELEASE_CERT_SHA256" ]; then
|
||||
echo "::error title=Wrong APK signer::Android 9+ would see '$MODERN', expected only the release key $RELEASE_CERT_SHA256."
|
||||
exit 1
|
||||
fi
|
||||
if [ "$ROTATED" = "true" ]; then
|
||||
echo "::warning title=Android 7-8.1 still trust the debug key::The v2 signature is made with the debug key so that installs on Android 7-8.1 can update. Those devices remain exposed; see docs/android-release.md."
|
||||
elif [ "$LEGACY" != "$RELEASE_CERT_SHA256" ]; then
|
||||
echo "::error title=Wrong APK signer::Android 7-8.1 would see '$LEGACY', expected only the release key $RELEASE_CERT_SHA256."
|
||||
exit 1
|
||||
fi
|
||||
aapt2 dump badging "$APK" | grep '^package:' || true
|
||||
|
||||
# The R8 mapping goes out with the APK: without the one from this exact
|
||||
# build, the obfuscated frames in a release stack trace cannot be
|
||||
# decoded (docs/android-release.md, "Stack traces").
|
||||
- name: Rename APK and R8 mapping with version
|
||||
id: artifact
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VERSION=$(cat VERSION | tr -d '[:space:]')
|
||||
SRC=android/app/build/outputs/apk/release/app-release.apk
|
||||
DEST="bulwark-mobile-${VERSION}-${GITHUB_SHA::7}.apk"
|
||||
cp "$SRC" "$DEST"
|
||||
|
||||
MAPPING_SRC=android/app/build/outputs/mapping/release/mapping.txt
|
||||
MAPPING=""
|
||||
if [ -f "$MAPPING_SRC" ]; then
|
||||
MAPPING="bulwark-mobile-${VERSION}-${GITHUB_SHA::7}-mapping.txt"
|
||||
cp "$MAPPING_SRC" "$MAPPING"
|
||||
elif grep -Eq '^android\.enableMinifyInReleaseBuilds=true\s*$' android/gradle.properties; then
|
||||
echo "::error title=R8 mapping missing::R8 is on but the build wrote no $MAPPING_SRC, so stack traces from this APK could not be decoded."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "path=$DEST" >> "$GITHUB_OUTPUT"
|
||||
echo "mapping=$MAPPING" >> "$GITHUB_OUTPUT"
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "files<<FILES_EOF"
|
||||
echo "$DEST"
|
||||
if [ -n "$MAPPING" ]; then echo "$MAPPING"; fi
|
||||
echo "FILES_EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -106,6 +268,13 @@ jobs:
|
||||
path: ${{ steps.artifact.outputs.path }}
|
||||
if-no-files-found: error
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: steps.artifact.outputs.mapping != ''
|
||||
with:
|
||||
name: ${{ steps.artifact.outputs.mapping }}
|
||||
path: ${{ steps.artifact.outputs.mapping }}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Resolve target release tag
|
||||
id: target_tag
|
||||
env:
|
||||
@@ -125,9 +294,9 @@ jobs:
|
||||
echo "No release tag resolved - APK is in workflow artifacts only."
|
||||
fi
|
||||
|
||||
- name: Attach APK to release
|
||||
- name: Attach APK and R8 mapping to release
|
||||
if: steps.target_tag.outputs.tag != ''
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ steps.target_tag.outputs.tag }}
|
||||
files: ${{ steps.artifact.outputs.path }}
|
||||
files: ${{ steps.artifact.outputs.files }}
|
||||
|
||||
@@ -17,6 +17,9 @@ expo-env.d.ts
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.keystore
|
||||
!/android/app/debug.keystore
|
||||
*.lineage
|
||||
|
||||
# Metro / Expo dev-server logs
|
||||
.metro-health-check*
|
||||
|
||||
@@ -2,7 +2,9 @@ import React from 'react';
|
||||
import { ActivityIndicator, AppState, Linking, StyleSheet, Text, View } from 'react-native';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { useColorScheme } from 'react-native';
|
||||
import { NavigationContainer, createNavigationContainerRef } from '@react-navigation/native';
|
||||
import {
|
||||
DarkTheme, DefaultTheme, NavigationContainer, createNavigationContainerRef, useIsFocused,
|
||||
} from '@react-navigation/native';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
import { createNativeStackNavigator, type NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
import { Mail, Calendar, BookUser, HardDrive, Settings } from 'lucide-react-native';
|
||||
@@ -11,6 +13,15 @@ import { startLiveUpdates, type LiveUpdatesHandle } from './src/api/push-stream'
|
||||
import { jmapClient } from './src/api/jmap-client';
|
||||
import type { StateChange } from './src/api/types';
|
||||
import { dispatchStateChange } from './src/lib/state-change-bus';
|
||||
import {
|
||||
startCalendarNotificationSync,
|
||||
startCalendarReminderTapHandling,
|
||||
} from './src/lib/calendar-notifications';
|
||||
import { startLivenessMonitor } from './src/lib/connection-liveness';
|
||||
import {
|
||||
setPendingCalendarOpen,
|
||||
type CalendarReminderTarget,
|
||||
} from './src/navigation/pending-calendar-open';
|
||||
import { sweepStaleExportFiles } from './src/lib/email-export';
|
||||
import { useFilterStore } from './src/stores/filter-store';
|
||||
import { useVacationStore } from './src/stores/vacation-store';
|
||||
@@ -20,10 +31,12 @@ import {
|
||||
addTokenRefreshListener,
|
||||
getInitialNotificationTap,
|
||||
getStoredRelayBaseUrl,
|
||||
setupPushNotifications,
|
||||
notificationTapJmapAccountId,
|
||||
resyncPushNotifications,
|
||||
teardownPushNotificationsForAccount,
|
||||
type NotificationTapPayload,
|
||||
} from './src/lib/push-notifications';
|
||||
import { addUnifiedPushEndpointListener } from './src/lib/unified-push';
|
||||
import type { MainTabsParamList, RootStackParamList } from './src/navigation/types';
|
||||
import ComposeScreen from './src/screens/ComposeScreen';
|
||||
import EmailThreadScreen from './src/screens/EmailThreadScreen';
|
||||
@@ -43,7 +56,9 @@ import { useAccountStore } from './src/stores/account-store';
|
||||
import { useAuthStore } from './src/stores/auth-store';
|
||||
import { useCalendarStore } from './src/stores/calendar-store';
|
||||
import { useContactsStore } from './src/stores/contacts-store';
|
||||
import { useEmailStore } from './src/stores/email-store';
|
||||
import { useEmailStore, viewerParamsForRow } from './src/stores/email-store';
|
||||
import { mailboxAccountId } from './src/lib/mailbox-tree';
|
||||
import { prefetchMessage } from './src/lib/email-detail-cache';
|
||||
import { useHasCalendar, useHasContacts, useHasFiles } from './src/lib/capabilities';
|
||||
import { useSettingsStore } from './src/stores/settings-store';
|
||||
import { useLocaleStore } from './src/stores/locale-store';
|
||||
@@ -52,6 +67,8 @@ import { useUpdatesStore } from './src/stores/updates-store';
|
||||
import { UpdateBanner } from './src/components/UpdateBanner';
|
||||
import { PushOnboardingPrompt } from './src/components/PushOnboardingPrompt';
|
||||
import { ToastHost } from './src/components/ToastHost';
|
||||
import { UndoSnackbar } from './src/components/UndoSnackbar';
|
||||
import { AppIconBadge } from './src/components/AppIconBadge';
|
||||
import { getEmails } from './src/api/email';
|
||||
import { handleDeepLink, parseDeepLink, shareToDeepLink, type DeepLink } from './src/navigation/linking';
|
||||
import { addShareListener, getInitialShare, shareAttachments } from './src/lib/share-intent';
|
||||
@@ -81,13 +98,36 @@ async function navigateToNotificationTap(payload: NotificationTapPayload): Promi
|
||||
if (useAuthStore.getState().activeAccountId !== payload.accountId) return;
|
||||
}
|
||||
|
||||
prefetchMessage({ id: payload.emailId, threadId: payload.threadId });
|
||||
navigationRef.navigate('EmailThread', {
|
||||
emailId: payload.emailId,
|
||||
threadId: payload.threadId,
|
||||
subject: payload.subject,
|
||||
// A group mailbox's message lives under another JMAP account (#839).
|
||||
jmapAccountId: notificationTapJmapAccountId(payload),
|
||||
});
|
||||
}
|
||||
|
||||
// A tapped calendar reminder opens its event (or task) in the Calendar tab,
|
||||
// under the account it was scheduled for. The target is parked for
|
||||
// CalendarScreen, which also picks it up when it mounts after a cold start.
|
||||
async function openCalendarReminder(target: CalendarReminderTarget): Promise<void> {
|
||||
const auth = useAuthStore.getState();
|
||||
if (target.appAccountId && target.appAccountId !== auth.activeAccountId) {
|
||||
if (!useAccountStore.getState().getAccountById(target.appAccountId)) return;
|
||||
await auth.switchAccount(target.appAccountId);
|
||||
if (useAuthStore.getState().activeAccountId !== target.appAccountId) return;
|
||||
}
|
||||
setPendingCalendarOpen(target);
|
||||
// On a cold start the navigator mounts right after the auth gate flips.
|
||||
for (let attempt = 0; attempt < 50 && !navigationRef.isReady(); attempt++) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
if (navigationRef.isReady()) {
|
||||
navigationRef.navigate('MainTabs', { screen: 'Calendar' } as never);
|
||||
}
|
||||
}
|
||||
|
||||
// Deep links (bulwarkmobile://, webmail https permalinks, mailto:) and
|
||||
// Android share-sheet payloads all end up here once the navigator is ready.
|
||||
async function openDeepLink(link: DeepLink): Promise<void> {
|
||||
@@ -111,6 +151,33 @@ async function openDeepLink(link: DeepLink): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
// Toasts and the undo bar show on whichever stack screen is up, so a failure
|
||||
// that lands after leaving the screen it came from (a viewer action finishing
|
||||
// on the Unified Inbox or a contact) is still seen, and a reply held by the
|
||||
// undo-send delay can be undone from the viewer it was sent from. Every
|
||||
// screen carries the hosts, and only the focused one renders them, so
|
||||
// neither ever shows twice (the screen below the top one is kept live).
|
||||
// The composer gets no undo bar: its Undo would hand the open composer
|
||||
// another draft. A held send's bar shows on the screen below once it closes.
|
||||
function FocusedHosts({ undo }: { undo: boolean }) {
|
||||
if (!useIsFocused()) return null;
|
||||
return (
|
||||
<>
|
||||
{undo && <UndoSnackbar />}
|
||||
<ToastHost />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function withHosts({ route, children }: { route: { name: string }; children: React.ReactElement }) {
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<FocusedHosts undo={route.name !== 'Compose'} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function LoadingScreen({ message }: { message: string }) {
|
||||
const c = useColors();
|
||||
return (
|
||||
@@ -123,23 +190,38 @@ function LoadingScreen({ message }: { message: string }) {
|
||||
|
||||
function MainTabsNavigator({ navigation }: NativeStackScreenProps<RootStackParamList, 'MainTabs'>) {
|
||||
const c = useColors();
|
||||
const mailboxes = useEmailStore((state) => state.mailboxes);
|
||||
// Select the count, not the list: every mailbox fetch sets a new array, and
|
||||
// re-rendering here re-renders the mail list with it.
|
||||
const inboxUnreadCount = useEmailStore(
|
||||
(state) => state.mailboxes.find((mailbox) => mailbox.role === 'inbox')?.unreadEmails ?? 0,
|
||||
);
|
||||
const logout = useAuthStore((state) => state.logout);
|
||||
const inboxUnreadCount = mailboxes.find((mailbox) => mailbox.role === 'inbox')?.unreadEmails ?? 0;
|
||||
const hasCalendar = useHasCalendar();
|
||||
const hasContacts = useHasContacts();
|
||||
const hasFiles = useHasFiles();
|
||||
const disabledTabStyle = { opacity: 0.4 } as const;
|
||||
const t = useLocaleStore((state) => state.t);
|
||||
// The webmail's navigation names; a tab the server lacks says so.
|
||||
const tabLabels = {
|
||||
mail: t('sidebar.mail', 'Mail'),
|
||||
calendar: t('sidebar.calendar', 'Calendar'),
|
||||
contacts: t('sidebar.contacts', 'Contacts'),
|
||||
files: t('sidebar.files', 'Files'),
|
||||
settings: t('sidebar.settings', 'Settings'),
|
||||
};
|
||||
const unavailable = (name: string) => t('sidebar.tab_unavailable', '{name} (unavailable)', { name });
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: c.background }}>
|
||||
<UpdateBanner />
|
||||
<OfflineCacheBanner />
|
||||
<PushOnboardingPrompt />
|
||||
<ToastHost />
|
||||
<AppIconBadge />
|
||||
<Tab.Navigator
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
// Tabs out of view stop re-rendering until they are shown again.
|
||||
freezeOnBlur: true,
|
||||
tabBarActiveTintColor: c.text,
|
||||
tabBarInactiveTintColor: c.textSecondary,
|
||||
tabBarStyle: {
|
||||
@@ -159,6 +241,7 @@ function MainTabsNavigator({ navigation }: NativeStackScreenProps<RootStackParam
|
||||
<Tab.Screen
|
||||
name="Mail"
|
||||
options={{
|
||||
title: tabLabels.mail,
|
||||
tabBarIcon: ({ color, size }) => <Mail size={size} color={color} />,
|
||||
tabBarBadge: inboxUnreadCount > 0 ? (inboxUnreadCount > 99 ? '99+' : inboxUnreadCount) : undefined,
|
||||
tabBarBadgeStyle: {
|
||||
@@ -179,10 +262,22 @@ function MainTabsNavigator({ navigation }: NativeStackScreenProps<RootStackParam
|
||||
<EmailListScreen
|
||||
onComposePress={() => navigation.navigate('Compose')}
|
||||
onEmailPress={(email) => {
|
||||
// The list holds the open folder's mail: name its account so the
|
||||
// viewer never has to guess it from whatever folder is open.
|
||||
const { mailboxes: all, currentMailboxId } = useEmailStore.getState();
|
||||
const target = {
|
||||
jmapAccountId: mailboxAccountId(all, currentMailboxId),
|
||||
// A row of an "All folders" list or a tag view names its own
|
||||
// account instead, and pages over that account's rows.
|
||||
...viewerParamsForRow(email),
|
||||
};
|
||||
// Start on the body now: mounting the viewer takes a while.
|
||||
prefetchMessage(email, target.jmapAccountId);
|
||||
navigation.navigate('EmailThread', {
|
||||
emailId: email.id,
|
||||
threadId: email.threadId,
|
||||
subject: email.subject,
|
||||
...target,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -192,9 +287,10 @@ function MainTabsNavigator({ navigation }: NativeStackScreenProps<RootStackParam
|
||||
name="Calendar"
|
||||
component={CalendarScreen}
|
||||
options={{
|
||||
title: tabLabels.calendar,
|
||||
tabBarIcon: ({ color, size }) => <Calendar size={size} color={color} />,
|
||||
tabBarItemStyle: hasCalendar ? undefined : disabledTabStyle,
|
||||
tabBarAccessibilityLabel: hasCalendar ? 'Calendar' : 'Calendar (unavailable)',
|
||||
tabBarAccessibilityLabel: hasCalendar ? tabLabels.calendar : unavailable(tabLabels.calendar),
|
||||
}}
|
||||
listeners={{
|
||||
tabPress: (e) => {
|
||||
@@ -206,9 +302,10 @@ function MainTabsNavigator({ navigation }: NativeStackScreenProps<RootStackParam
|
||||
name="Contacts"
|
||||
component={ContactsScreen}
|
||||
options={{
|
||||
title: tabLabels.contacts,
|
||||
tabBarIcon: ({ color, size }) => <BookUser size={size} color={color} />,
|
||||
tabBarItemStyle: hasContacts ? undefined : disabledTabStyle,
|
||||
tabBarAccessibilityLabel: hasContacts ? 'Contacts' : 'Contacts (unavailable)',
|
||||
tabBarAccessibilityLabel: hasContacts ? tabLabels.contacts : unavailable(tabLabels.contacts),
|
||||
}}
|
||||
listeners={{
|
||||
tabPress: (e) => {
|
||||
@@ -220,9 +317,10 @@ function MainTabsNavigator({ navigation }: NativeStackScreenProps<RootStackParam
|
||||
name="Files"
|
||||
component={FilesScreen}
|
||||
options={{
|
||||
title: tabLabels.files,
|
||||
tabBarIcon: ({ color, size }) => <HardDrive size={size} color={color} />,
|
||||
tabBarItemStyle: hasFiles ? undefined : disabledTabStyle,
|
||||
tabBarAccessibilityLabel: hasFiles ? 'Files' : 'Files (unavailable)',
|
||||
tabBarAccessibilityLabel: hasFiles ? tabLabels.files : unavailable(tabLabels.files),
|
||||
}}
|
||||
listeners={{
|
||||
tabPress: (e) => {
|
||||
@@ -233,6 +331,7 @@ function MainTabsNavigator({ navigation }: NativeStackScreenProps<RootStackParam
|
||||
<Tab.Screen
|
||||
name="Settings"
|
||||
options={{
|
||||
title: tabLabels.settings,
|
||||
tabBarIcon: ({ color, size }) => <Settings size={size} color={color} />,
|
||||
}}
|
||||
>
|
||||
@@ -248,6 +347,7 @@ export default function App() {
|
||||
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
||||
const client = useAuthStore((state) => state.client);
|
||||
const restoreSession = useAuthStore((state) => state.restoreSession);
|
||||
const t = useLocaleStore((state) => state.t);
|
||||
|
||||
// Resolve the user's theme preference to a concrete light/dark style for the
|
||||
// system status bar. The rest of the app's colors are still hard-coded dark
|
||||
@@ -257,6 +357,13 @@ export default function App() {
|
||||
const resolvedScheme: 'light' | 'dark' =
|
||||
themePref === 'system' ? (systemScheme === 'light' ? 'light' : 'dark') : themePref;
|
||||
const statusBarStyle: 'light' | 'dark' = resolvedScheme === 'light' ? 'dark' : 'light';
|
||||
// React Navigation's default theme is light: without this its containers
|
||||
// paint white behind and between screens, even in dark mode.
|
||||
const background = useColors().background;
|
||||
const navigationTheme = React.useMemo(() => {
|
||||
const base = resolvedScheme === 'light' ? DefaultTheme : DarkTheme;
|
||||
return { ...base, colors: { ...base.colors, background, card: background } };
|
||||
}, [resolvedScheme, background]);
|
||||
// Persisted active account is the signal that the user was already signed
|
||||
// in on the previous launch. When present we render the main UI with the
|
||||
// cached mail list instead of the "Restoring session" spinner; the real
|
||||
@@ -272,6 +379,9 @@ export default function App() {
|
||||
React.useEffect(() => {
|
||||
void useSettingsStore.getState().hydrate();
|
||||
void useLocaleStore.getState().hydrate();
|
||||
// Calendar reminders are kept scheduled from launch, not only once the
|
||||
// Calendar tab has been opened.
|
||||
startCalendarNotificationSync();
|
||||
return useNetworkStore.getState().init();
|
||||
}, []);
|
||||
|
||||
@@ -376,6 +486,15 @@ export default function App() {
|
||||
};
|
||||
}, [isAuthenticated]);
|
||||
|
||||
// Calendar reminders are local expo notifications; their taps come through
|
||||
// expo-notifications, not the native tap store used for mail push above.
|
||||
React.useEffect(() => {
|
||||
if (!isAuthenticated) return;
|
||||
return startCalendarReminderTapHandling((target) => {
|
||||
void openCalendarReminder(target);
|
||||
});
|
||||
}, [isAuthenticated]);
|
||||
|
||||
// Deep links and share-sheet payloads. The cold-start URL / share is read
|
||||
// once auth is restored so the target screen has credentials.
|
||||
React.useEffect(() => {
|
||||
@@ -426,7 +545,8 @@ export default function App() {
|
||||
// and whenever the FCM token rotates. Honours the user's notification
|
||||
// preference - flipping it off tears down THIS account's subscription so
|
||||
// notifications stop arriving for it. Other logged-in accounts keep their
|
||||
// setups intact.
|
||||
// setups intact. An account the user turned push off for, or whose device
|
||||
// registration was revoked, stays off (see resyncPushNotifications).
|
||||
const emailNotificationsEnabled = useSettingsStore(
|
||||
(s) => s.emailNotificationsEnabled,
|
||||
);
|
||||
@@ -447,7 +567,7 @@ export default function App() {
|
||||
const relayBaseUrl = await getStoredRelayBaseUrl();
|
||||
if (!relayBaseUrl) return;
|
||||
try {
|
||||
await setupPushNotifications({
|
||||
await resyncPushNotifications({
|
||||
relayBaseUrl,
|
||||
accountLabel: client.username ?? undefined,
|
||||
});
|
||||
@@ -464,10 +584,16 @@ export default function App() {
|
||||
const unsubscribe = addTokenRefreshListener(() => {
|
||||
void doSetup();
|
||||
});
|
||||
// UnifiedPush equivalent of an FCM token rotation: the distributor handed
|
||||
// out a new endpoint, so re-register it with the relay.
|
||||
const unsubscribeUp = addUnifiedPushEndpointListener(() => {
|
||||
void doSetup();
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
unsubscribe();
|
||||
unsubscribeUp();
|
||||
};
|
||||
}, [client, isAuthenticated, emailNotificationsEnabled, activeAccountId]);
|
||||
|
||||
@@ -528,6 +654,23 @@ export default function App() {
|
||||
void useOutboxStore.getState().flush();
|
||||
};
|
||||
|
||||
// Foreground liveness for the per-account connection dot: the open
|
||||
// stream's pings, or a `Core/echo` every 30 s while there is none (see
|
||||
// startLivenessMonitor). NetInfo cannot tell an unreachable server.
|
||||
const liveness = startLivenessMonitor({
|
||||
ping: () => jmapClient.ping(),
|
||||
streamHealthy: () => handle?.healthy ?? false,
|
||||
isActive: () => appActive,
|
||||
isOnline: () => useNetworkStore.getState().online,
|
||||
onConnected: (connected) => {
|
||||
if (!mounted || !activeAccountId) return;
|
||||
const account = useAccountStore.getState().getAccountById(activeAccountId);
|
||||
if (account && account.isConnected !== connected) {
|
||||
useAccountStore.getState().updateAccount(activeAccountId, { isConnected: connected });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
void start();
|
||||
|
||||
const subscription = AppState.addEventListener('change', (state) => {
|
||||
@@ -539,19 +682,12 @@ export default function App() {
|
||||
handle = null;
|
||||
return;
|
||||
}
|
||||
// Coming back: verify the session is still alive before trusting the
|
||||
// stream, then reconnect and catch up on what was missed.
|
||||
void (async () => {
|
||||
const alive = await jmapClient.ping().catch(() => false);
|
||||
if (!mounted) return;
|
||||
if (!alive && useNetworkStore.getState().online) {
|
||||
const ok = await useAuthStore.getState().retrySession().catch(() => false);
|
||||
if (!ok || !mounted) return;
|
||||
}
|
||||
if (!handle) await start();
|
||||
else handle.reconnect();
|
||||
refreshAfterResume();
|
||||
})();
|
||||
// Coming back: reconnect and catch up on what was missed at once;
|
||||
// one echo alongside checks the server for the connection dot.
|
||||
if (!handle) void start();
|
||||
else handle.reconnect();
|
||||
refreshAfterResume();
|
||||
void liveness.check();
|
||||
});
|
||||
|
||||
// Reconnect when the network comes back while foregrounded.
|
||||
@@ -566,39 +702,12 @@ export default function App() {
|
||||
mounted = false;
|
||||
subscription.remove();
|
||||
unsubscribeNetwork();
|
||||
liveness.stop();
|
||||
handle?.close();
|
||||
handle = null;
|
||||
};
|
||||
}, [client, isAuthenticated, haveLiveSession, activeAccountId]);
|
||||
|
||||
// Foreground keep-alive: a `Core/echo` every 30 s tells us when the server
|
||||
// is unreachable even though the device is online (NetInfo cannot), and
|
||||
// drives the per-account connection dot.
|
||||
React.useEffect(() => {
|
||||
if (!isAuthenticated || !haveLiveSession || !activeAccountId) return;
|
||||
let cancelled = false;
|
||||
let failures = 0;
|
||||
const timer = setInterval(() => {
|
||||
if (AppState.currentState !== 'active') return;
|
||||
if (!useNetworkStore.getState().online) return;
|
||||
void jmapClient.ping().then((ok) => {
|
||||
if (cancelled) return;
|
||||
failures = ok ? 0 : failures + 1;
|
||||
const account = useAccountStore.getState().getAccountById(activeAccountId);
|
||||
if (!account) return;
|
||||
// One missed echo can be a blip; two in a row is a lost connection.
|
||||
const connected = failures < 2;
|
||||
if (account.isConnected !== connected) {
|
||||
useAccountStore.getState().updateAccount(activeAccountId, { isConnected: connected });
|
||||
}
|
||||
}).catch(() => undefined);
|
||||
}, 30_000);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, [isAuthenticated, haveLiveSession, activeAccountId]);
|
||||
|
||||
// Skip the "Restoring session" flash for returning users: if we already
|
||||
// have a persisted active account, render the main UI immediately with
|
||||
// whatever the email-store hydrated from cache. restoreSession still runs
|
||||
@@ -607,7 +716,7 @@ export default function App() {
|
||||
return (
|
||||
<>
|
||||
<StatusBar style={statusBarStyle} />
|
||||
<LoadingScreen message="Loading..." />
|
||||
<LoadingScreen message={t('common.loading', 'Loading...')} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -622,9 +731,11 @@ export default function App() {
|
||||
}
|
||||
|
||||
return (
|
||||
<NavigationContainer ref={navigationRef}>
|
||||
<NavigationContainer ref={navigationRef} theme={navigationTheme}>
|
||||
<StatusBar style={statusBarStyle} />
|
||||
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
||||
{/* On Fabric, native-stack keeps the screen right below the top live and
|
||||
freezes the ones further down. */}
|
||||
<Stack.Navigator screenOptions={{ headerShown: false, freezeOnBlur: true }} screenLayout={withHosts}>
|
||||
<Stack.Screen name="MainTabs" component={MainTabsNavigator} />
|
||||
<Stack.Screen name="EmailThread" component={EmailThreadScreen} />
|
||||
<Stack.Screen name="EmailSource" component={EmailSourceScreen} />
|
||||
|
||||
+25
-14
@@ -75,23 +75,34 @@ commit hash. The remaining `- [ ]` items are either deferred with a note
|
||||
for TOTP accounts, relay APNs/UnifiedPush transports, a server-side settings
|
||||
store for native #1).
|
||||
|
||||
## Status after the 2026-09-24 fix pass
|
||||
|
||||
The native audit of 2026-09-22 ([docs/audit-2026-09.md](docs/audit-2026-09.md))
|
||||
spot-checked this list and found 14 ticks that were broken or overstated and 11
|
||||
open items that were already done. The 2026-09-24 fix pass fixed 13 of the 14
|
||||
(their ticks now also name the fixing commit; the date-locale item is open
|
||||
again), dropped three obsolete items and ticked what it completed: 380 of 415
|
||||
items are done, 35 open. What still needs a device check or a decision is in
|
||||
the audit's "Fix pass, 2026-09-24" section.
|
||||
|
||||
## Areas
|
||||
|
||||
| # | Area | File | Items | P1 | P2 | P3 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| 01 | Authentication, login, session, multi-account | [docs/parity/01-auth-accounts.md](docs/parity/01-auth-accounts.md) | 30 | 4 | 8 | 18 |
|
||||
| 02 | Mail list, folders, unified views, search, tags | [docs/parity/02-mail-list-folders.md](docs/parity/02-mail-list-folders.md) | 54 | 1 | 20 | 33 |
|
||||
| 03 | Email viewer, thread view, rendering, attachments | [docs/parity/03-email-viewer.md](docs/parity/03-email-viewer.md) | 46 | 6 | 13 | 26 |
|
||||
| 04 | Composer, drafts, sending, identities, templates, scheduled send | [docs/parity/04-composer-send.md](docs/parity/04-composer-send.md) | 51 | 5 | 14 | 32 |
|
||||
| 05 | Calendar and tasks | [docs/parity/05-calendar.md](docs/parity/05-calendar.md) | 51 | 5 | 20 | 26 |
|
||||
| 06 | Contacts and address books | [docs/parity/06-contacts.md](docs/parity/06-contacts.md) | 51 | 1 | 18 | 31 |
|
||||
| 07 | Filters (Sieve), vacation responder, Files | [docs/parity/07-filters-vacation-files.md](docs/parity/07-filters-vacation-files.md) | 33 | 3 | 8 | 22 |
|
||||
| 08 | Settings, sync, push, i18n, themes, updates, misc UI | [docs/parity/08-settings-push-i18n-ui.md](docs/parity/08-settings-push-i18n-ui.md) | 46 | 0 | 14 | 32 |
|
||||
| 09 | JMAP client core, live sync, offline, security, S/MIME | [docs/parity/09-jmap-core-sync-security.md](docs/parity/09-jmap-core-sync-security.md) | 56 | 7 | 23 | 26 |
|
||||
| | **Total** | | **418** | **32** | **138** | **246** |
|
||||
| # | Area | File | Items | Done | Open | P1 | P2 | P3 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| 01 | Authentication, login, session, multi-account | [docs/parity/01-auth-accounts.md](docs/parity/01-auth-accounts.md) | 30 | 29 | 1 | 4 | 8 | 18 |
|
||||
| 02 | Mail list, folders, unified views, search, tags | [docs/parity/02-mail-list-folders.md](docs/parity/02-mail-list-folders.md) | 54 | 50 | 4 | 1 | 20 | 33 |
|
||||
| 03 | Email viewer, thread view, rendering, attachments | [docs/parity/03-email-viewer.md](docs/parity/03-email-viewer.md) | 46 | 45 | 1 | 6 | 13 | 26 |
|
||||
| 04 | Composer, drafts, sending, identities, templates, scheduled send | [docs/parity/04-composer-send.md](docs/parity/04-composer-send.md) | 51 | 46 | 5 | 5 | 14 | 32 |
|
||||
| 05 | Calendar and tasks | [docs/parity/05-calendar.md](docs/parity/05-calendar.md) | 50 | 43 | 7 | 5 | 20 | 25 |
|
||||
| 06 | Contacts and address books | [docs/parity/06-contacts.md](docs/parity/06-contacts.md) | 50 | 46 | 4 | 1 | 18 | 31 |
|
||||
| 07 | Filters (Sieve), vacation responder, Files | [docs/parity/07-filters-vacation-files.md](docs/parity/07-filters-vacation-files.md) | 32 | 29 | 3 | 3 | 8 | 21 |
|
||||
| 08 | Settings, sync, push, i18n, themes, updates, misc UI | [docs/parity/08-settings-push-i18n-ui.md](docs/parity/08-settings-push-i18n-ui.md) | 46 | 38 | 8 | 0 | 14 | 32 |
|
||||
| 09 | JMAP client core, live sync, offline, security, S/MIME | [docs/parity/09-jmap-core-sync-security.md](docs/parity/09-jmap-core-sync-security.md) | 56 | 54 | 2 | 7 | 23 | 26 |
|
||||
| | **Total** | | **415** | **380** | **35** | **32** | **138** | **244** |
|
||||
|
||||
Counts are of `- [ ]` items per file (a handful of "verified" notes carry a
|
||||
priority tag too, so P-sums can differ by one or two from the item count).
|
||||
Counts are of the `- [ ]` and `- [x]` items per file as of 2026-09-24. Done and
|
||||
Open split them by tick; the P columns count the priority tags on those items
|
||||
(one item carries none).
|
||||
|
||||
## All P1 findings (fix these first)
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ React Native (Expo SDK 54) client for [Bulwark Webmail](https://github.com/bulwa
|
||||
- Email list, threads, compose
|
||||
- Calendar (basic)
|
||||
- Contacts (basic)
|
||||
- Push notifications via FCM relay
|
||||
- Push notifications via the Bulwark relay - FCM by default, or [UnifiedPush](https://unifiedpush.org) (e.g. ntfy) for devices without Google Play services
|
||||
- In-app sideload updates from GitHub Releases
|
||||
|
||||
## What's missing or rough
|
||||
@@ -40,7 +40,7 @@ npx expo start
|
||||
|
||||
Then press `a` for Android, `i` for iOS, or scan the QR with Expo Go.
|
||||
|
||||
For a release APK build see [.github/workflows/release-android.yml](.github/workflows/release-android.yml).
|
||||
For release APK builds and signing see [docs/android-release.md](docs/android-release.md).
|
||||
For iOS builds and TestFlight distribution see [docs/ios-release.md](docs/ios-release.md).
|
||||
|
||||
## License
|
||||
|
||||
+117
-8
@@ -7,6 +7,62 @@ def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
|
||||
|
||||
def appVersionName = file("${projectRoot}/VERSION").text.trim()
|
||||
|
||||
/**
|
||||
* versionCode has to grow with every release, or Android refuses the update as
|
||||
* a downgrade. It is derived from VERSION as
|
||||
*
|
||||
* major * 10000000 + minor * 10000 + patch (0.1.62 -> 10062)
|
||||
*
|
||||
* which keeps increasing as long as patch stays below 10000 and minor below
|
||||
* 1000 (scripts/bump-version.mjs, which only ever bumps patch, enforces both),
|
||||
* and stays under Android's 2100000000 ceiling up to major 209.
|
||||
*/
|
||||
def versionParts = (appVersionName =~ /^(\d+)\.(\d+)\.(\d+)$/)
|
||||
if (!versionParts.matches()) {
|
||||
throw new GradleException("VERSION must be MAJOR.MINOR.PATCH, got '${appVersionName}'")
|
||||
}
|
||||
def (versionMajor, versionMinor, versionPatch) = (1..3).collect { versionParts.group(it) as long }
|
||||
if (versionMinor > 999 || versionPatch > 9999) {
|
||||
throw new GradleException("VERSION ${appVersionName}: minor must stay below 1000 and patch below 10000, " +
|
||||
"or versionCode stops increasing. Move to the next minor or major version instead.")
|
||||
}
|
||||
def appVersionCode = versionMajor * 10000000L + versionMinor * 10000L + versionPatch
|
||||
if (appVersionCode < 1 || appVersionCode > 2100000000L) {
|
||||
throw new GradleException("VERSION ${appVersionName} gives versionCode ${appVersionCode}, " +
|
||||
"outside the 1..2100000000 range Android accepts.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Release signing. The key never lives in the repo: CI passes it as
|
||||
* ORG_GRADLE_PROJECT_BULWARK_RELEASE_* environment variables, and a local
|
||||
* release build can set the same names with -P, in ~/.gradle/gradle.properties
|
||||
* or as plain environment variables. See docs/android-release.md.
|
||||
*
|
||||
* There is deliberately no automatic fallback to the debug key. It is the
|
||||
* public React Native template key, so an APK signed with it can be replaced
|
||||
* by an "update" anyone can sign.
|
||||
*/
|
||||
def releaseSigningNames = [
|
||||
'BULWARK_RELEASE_STORE_FILE',
|
||||
'BULWARK_RELEASE_STORE_PASSWORD',
|
||||
'BULWARK_RELEASE_KEY_ALIAS',
|
||||
'BULWARK_RELEASE_KEY_PASSWORD',
|
||||
]
|
||||
def releaseSigning = releaseSigningNames.collectEntries { name ->
|
||||
[(name): (findProperty(name) ?: System.getenv(name))?.toString()]
|
||||
}
|
||||
def missingReleaseSigning = releaseSigningNames.findAll { !releaseSigning[it]?.trim() }
|
||||
def releaseSigningConfigured = missingReleaseSigning.isEmpty()
|
||||
def releaseStoreFile = releaseSigningConfigured ? file(releaseSigning.BULWARK_RELEASE_STORE_FILE.trim()) : null
|
||||
|
||||
// Escape hatch for a local release build that never leaves the machine
|
||||
// (profiling, checking R8). It is refused whenever CI is set, so a stray flag
|
||||
// cannot turn a published build back into a debug-signed one.
|
||||
def ciEnv = System.getenv('CI')?.trim()?.toLowerCase()
|
||||
def onCi = ciEnv && !(ciEnv in ['false', '0'])
|
||||
def allowDebugSignedRelease = (findProperty('allowDebugSignedRelease') ?: 'false').toString().trim().toBoolean()
|
||||
def debugSignedRelease = !releaseSigningConfigured && allowDebugSignedRelease && !onCi
|
||||
|
||||
/**
|
||||
* This is the configuration block to customize your React Native Android app.
|
||||
* By default you don't need to apply any configuration, just uncomment the lines you need.
|
||||
@@ -95,7 +151,7 @@ android {
|
||||
applicationId 'com.anonymous.bulwarkmobile'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionCode appVersionCode.toInteger()
|
||||
versionName appVersionName
|
||||
|
||||
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||
@@ -107,13 +163,12 @@ android {
|
||||
keyAlias 'androiddebugkey'
|
||||
keyPassword 'android'
|
||||
}
|
||||
if (project.hasProperty('BULWARK_RELEASE_STORE_FILE')
|
||||
&& project.property('BULWARK_RELEASE_STORE_FILE')?.trim()) {
|
||||
if (releaseSigningConfigured) {
|
||||
release {
|
||||
storeFile file(project.property('BULWARK_RELEASE_STORE_FILE'))
|
||||
storePassword project.property('BULWARK_RELEASE_STORE_PASSWORD')
|
||||
keyAlias project.property('BULWARK_RELEASE_KEY_ALIAS')
|
||||
keyPassword project.property('BULWARK_RELEASE_KEY_PASSWORD')
|
||||
storeFile releaseStoreFile
|
||||
storePassword releaseSigning.BULWARK_RELEASE_STORE_PASSWORD
|
||||
keyAlias releaseSigning.BULWARK_RELEASE_KEY_ALIAS
|
||||
keyPassword releaseSigning.BULWARK_RELEASE_KEY_PASSWORD
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,7 +177,13 @@ android {
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
release {
|
||||
signingConfig signingConfigs.findByName('release') ?: signingConfigs.debug
|
||||
// With neither a key nor the opt-in there is no signing config at
|
||||
// all; checkReleaseSigning below stops the build before packaging.
|
||||
if (releaseSigningConfigured) {
|
||||
signingConfig signingConfigs.release
|
||||
} else if (debugSignedRelease) {
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
def enableShrinkResources = findProperty('android.enableShrinkResourcesInReleaseBuilds') ?: 'false'
|
||||
shrinkResources enableShrinkResources.toBoolean()
|
||||
minifyEnabled enableMinifyInReleaseBuilds
|
||||
@@ -142,6 +203,48 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
// Stop a release build that has no real key. This runs only when a release
|
||||
// variant is actually built, so `expo run:android`, assembleDebug and IDE sync
|
||||
// keep working with no key configured.
|
||||
def releaseSigningHelp = [
|
||||
"Release signing is not configured (missing: ${missingReleaseSigning.join(', ')}).",
|
||||
"",
|
||||
"Release builds are never signed with the debug key: it is the public React Native",
|
||||
"template key, so an APK signed with it can be replaced by anyone. Provide the release",
|
||||
"key as gradle properties (-P or ~/.gradle/gradle.properties) or environment variables:",
|
||||
" BULWARK_RELEASE_STORE_FILE path to the keystore",
|
||||
" BULWARK_RELEASE_STORE_PASSWORD",
|
||||
" BULWARK_RELEASE_KEY_ALIAS",
|
||||
" BULWARK_RELEASE_KEY_PASSWORD",
|
||||
"",
|
||||
onCi && allowDebugSignedRelease
|
||||
? "allowDebugSignedRelease is ignored because CI is set: published builds must use the release key."
|
||||
: "For a local test build that is never installed anywhere else, pass -PallowDebugSignedRelease=true.",
|
||||
"See docs/android-release.md.",
|
||||
].join('\n')
|
||||
|
||||
def checkReleaseSigning = tasks.register('checkReleaseSigning') {
|
||||
group = 'verification'
|
||||
description = 'Fails a release build that has no release signing key.'
|
||||
doLast {
|
||||
if (releaseSigningConfigured) {
|
||||
if (!releaseStoreFile.isFile()) {
|
||||
throw new GradleException("BULWARK_RELEASE_STORE_FILE points to ${releaseStoreFile}, which does not exist.")
|
||||
}
|
||||
} else if (debugSignedRelease) {
|
||||
logger.warn("WARNING: this release build is signed with the PUBLIC debug key " +
|
||||
"(allowDebugSignedRelease). Do not distribute it.")
|
||||
} else {
|
||||
throw new GradleException(releaseSigningHelp)
|
||||
}
|
||||
}
|
||||
}
|
||||
tasks.configureEach { task ->
|
||||
if (task.name == 'preReleaseBuild') {
|
||||
task.dependsOn(checkReleaseSigning)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply static values from `gradle.properties` to the `android.packagingOptions`
|
||||
// Accepts values in comma delimited lists, example:
|
||||
// android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini
|
||||
@@ -171,6 +274,12 @@ dependencies {
|
||||
implementation(platform("com.google.firebase:firebase-bom:33.7.0"))
|
||||
implementation("com.google.firebase:firebase-messaging")
|
||||
|
||||
// UnifiedPush - Google-free push via a user-chosen distributor app such
|
||||
// as ntfy. Coexists with FCM; the user picks the transport in settings.
|
||||
// NOTE: 3.3.2-3.3.4 are built with Kotlin 2.3 and break this project's
|
||||
// Kotlin 2.1 toolchain; 3.3.5 went back to 2.2 which it can read.
|
||||
implementation("org.unifiedpush.android:connector:3.3.5")
|
||||
|
||||
def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
|
||||
def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true";
|
||||
def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true";
|
||||
|
||||
Vendored
+9
@@ -12,3 +12,12 @@
|
||||
-keep class com.facebook.react.turbomodule.** { *; }
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# The app's own native code (com.anonymous.bulwarkmobile). What R8 must not
|
||||
# drop or rename there is already covered elsewhere: React Native's rules keep
|
||||
# every NativeModule, whose @ReactMethod methods JS calls by name, and AAPT
|
||||
# keeps the application, activity and services listed in AndroidManifest.xml
|
||||
# (FCM, UnifiedPush and the headless JS task). Keeping the package whole on
|
||||
# top of that costs a few KB and leaves its class and method names readable
|
||||
# in a crash report from an obfuscated release build.
|
||||
-keep class com.anonymous.bulwarkmobile.** { *; }
|
||||
|
||||
@@ -59,6 +59,12 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service android:name=".BulwarkPushTaskService" android:exported="false"/>
|
||||
<service android:name=".BulwarkPushJobService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false"/>
|
||||
<service android:name=".BulwarkUnifiedPushService" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="org.unifiedpush.android.connector.PUSH_EVENT"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="bulwark_mail"/>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -66,6 +66,7 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext)
|
||||
val threadId = options.getString("threadId")
|
||||
val subject = options.takeIf { it.hasKey("subject") }?.getString("subject")
|
||||
val accountId = options.takeIf { it.hasKey("accountId") }?.getString("accountId")
|
||||
val jmapAccountId = options.takeIf { it.hasKey("jmapAccountId") }?.getString("jmapAccountId")
|
||||
val groupKey = options.takeIf { it.hasKey("groupKey") }?.getString("groupKey")
|
||||
?: accountId?.let { "bulwark-mail:$it" }
|
||||
val groupTitle = options.takeIf { it.hasKey("groupTitle") }?.getString("groupTitle")
|
||||
@@ -78,7 +79,7 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext)
|
||||
?: makeLetterAvatar(initials, bgColorHex)
|
||||
postNotification(
|
||||
notificationId, title, body, largeIcon, bgColorHex,
|
||||
emailId, threadId, subject, accountId, groupKey,
|
||||
emailId, threadId, subject, accountId, jmapAccountId, groupKey,
|
||||
)
|
||||
if (groupKey != null) postGroupSummary(groupKey, groupTitle, bgColorHex, accountId)
|
||||
promise.resolve(null)
|
||||
@@ -107,6 +108,7 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext)
|
||||
threadId: String?,
|
||||
subject: String?,
|
||||
accountId: String?,
|
||||
jmapAccountId: String?,
|
||||
groupKey: String?,
|
||||
) {
|
||||
val ctx = reactApplicationContext
|
||||
@@ -116,6 +118,7 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext)
|
||||
if (threadId != null) putExtra(NotificationTapStore.EXTRA_THREAD_ID, threadId)
|
||||
if (subject != null) putExtra(NotificationTapStore.EXTRA_SUBJECT, subject)
|
||||
if (accountId != null) putExtra(NotificationTapStore.EXTRA_ACCOUNT_ID, accountId)
|
||||
if (jmapAccountId != null) putExtra(NotificationTapStore.EXTRA_JMAP_ACCOUNT_ID, jmapAccountId)
|
||||
}
|
||||
val pending = PendingIntent.getActivity(
|
||||
ctx,
|
||||
@@ -125,7 +128,7 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext)
|
||||
)
|
||||
|
||||
val builder = NotificationCompat.Builder(ctx, BulwarkMessagingService.CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_email)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setLargeIcon(largeIcon)
|
||||
.setContentTitle(title)
|
||||
.setContentText(body)
|
||||
@@ -178,7 +181,7 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext)
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
||||
)
|
||||
val summary = NotificationCompat.Builder(ctx, BulwarkMessagingService.CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_email)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle(groupTitle)
|
||||
.setContentText(if (count == 1) "1 new message" else "$count new messages")
|
||||
.setStyle(inbox)
|
||||
|
||||
@@ -3,14 +3,20 @@ package com.anonymous.bulwarkmobile
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.bridge.NativeModule
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags
|
||||
import com.facebook.react.uimanager.ViewManager
|
||||
|
||||
class BulwarkFcmPackage : ReactPackage {
|
||||
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
|
||||
listOf(
|
||||
BulwarkFcmModule(reactContext),
|
||||
BulwarkClientCertModule(reactContext),
|
||||
)
|
||||
buildList {
|
||||
add(BulwarkFcmModule(reactContext))
|
||||
add(BulwarkUnifiedPushModule(reactContext))
|
||||
add(BulwarkClientCertModule(reactContext))
|
||||
// The bridge registers React Native's own module under this name.
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
add(BulwarkHeadlessJsTaskSupportModule(reactContext))
|
||||
}
|
||||
}
|
||||
|
||||
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
|
||||
emptyList()
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.anonymous.bulwarkmobile
|
||||
|
||||
import com.facebook.react.bridge.Promise
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
||||
import com.facebook.react.bridge.ReactMethod
|
||||
import com.facebook.react.jstasks.HeadlessJsTaskContext
|
||||
|
||||
/**
|
||||
* React Native's HeadlessJsTaskSupport module, which AppRegistry calls when a
|
||||
* headless JS task's promise settles. RN 0.81 registers it only for the old
|
||||
* bridge (CoreModulesPackage); the bridgeless runtime's CoreReactPackage
|
||||
* leaves it out, so in this app JS could never report a finished task and
|
||||
* every BulwarkPushTask ran until its 30 s timeout, keeping the service or job
|
||||
* and its wake lock alive that long. Same logic as the RN module.
|
||||
*/
|
||||
class BulwarkHeadlessJsTaskSupportModule(reactContext: ReactApplicationContext) :
|
||||
ReactContextBaseJavaModule(reactContext) {
|
||||
|
||||
override fun getName(): String = "HeadlessJsTaskSupport"
|
||||
|
||||
@ReactMethod
|
||||
fun notifyTaskFinished(taskId: Double) {
|
||||
val tasks = HeadlessJsTaskContext.getInstance(reactApplicationContext)
|
||||
val id = taskId.toInt()
|
||||
if (tasks.isTaskRunning(id)) tasks.finishTask(id)
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun notifyTaskRetry(taskId: Double, promise: Promise) {
|
||||
val tasks = HeadlessJsTaskContext.getInstance(reactApplicationContext)
|
||||
val id = taskId.toInt()
|
||||
promise.resolve(tasks.isTaskRunning(id) && tasks.retryTask(id))
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,9 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.PowerManager
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.facebook.react.HeadlessJsTaskService
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
@@ -31,7 +32,7 @@ class BulwarkMessagingService : FirebaseMessagingService() {
|
||||
// receives JMAP push directly, so the fcm:message event below is
|
||||
// sufficient for it to refresh state.
|
||||
if (!isAppInForeground()) {
|
||||
startHeadlessTask(data)
|
||||
startHeadlessTask(applicationContext, data)
|
||||
}
|
||||
|
||||
val params = Arguments.createMap().apply {
|
||||
@@ -42,25 +43,60 @@ class BulwarkMessagingService : FirebaseMessagingService() {
|
||||
BulwarkFcmModule.emit("fcm:message", params)
|
||||
}
|
||||
|
||||
private fun isAppInForeground(): Boolean {
|
||||
val info = ActivityManager.RunningAppProcessInfo()
|
||||
ActivityManager.getMyMemoryState(info)
|
||||
return info.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND ||
|
||||
info.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE
|
||||
}
|
||||
|
||||
private fun startHeadlessTask(data: Map<String, String>) {
|
||||
val intent = Intent(applicationContext, BulwarkPushTaskService::class.java)
|
||||
val bundle = Bundle().apply {
|
||||
for ((k, v) in data) putString(k, v)
|
||||
}
|
||||
intent.putExtras(bundle)
|
||||
applicationContext.startService(intent)
|
||||
HeadlessJsTaskService.acquireWakeLockNow(applicationContext)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CHANNEL_ID = "bulwark_mail"
|
||||
private const val TAG = "BulwarkPush"
|
||||
private const val START_WAKE_LOCK_MS = 10_000L
|
||||
|
||||
// Shared with BulwarkUnifiedPushService - a push arriving over either
|
||||
// transport is dispatched the same way.
|
||||
fun isAppInForeground(): Boolean {
|
||||
val info = ActivityManager.RunningAppProcessInfo()
|
||||
ActivityManager.getMyMemoryState(info)
|
||||
return info.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND ||
|
||||
info.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE
|
||||
}
|
||||
|
||||
fun startHeadlessTask(context: Context, data: Map<String, String>) {
|
||||
val intent = Intent(context, BulwarkPushTaskService::class.java)
|
||||
val bundle = Bundle().apply {
|
||||
for ((k, v) in data) putString(k, v)
|
||||
}
|
||||
intent.putExtras(bundle)
|
||||
// Keep the device awake until the service holds React Native's
|
||||
// wake lock; BulwarkPushTaskService releases this one then. Not
|
||||
// HeadlessJsTaskService.acquireWakeLockNow: it is not
|
||||
// thread-safe, and FCM calls this on a worker thread while the
|
||||
// service takes the same static lock on the main thread, so both
|
||||
// could create a lock and the overwritten one stayed held.
|
||||
startWakeLock = context.getSystemService(PowerManager::class.java)
|
||||
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "bulwark:push-start")
|
||||
.apply {
|
||||
setReferenceCounted(false)
|
||||
acquire(START_WAKE_LOCK_MS)
|
||||
}
|
||||
try {
|
||||
context.startService(intent)
|
||||
} catch (e: IllegalStateException) {
|
||||
// Android 8+ refuses to start a service from the background
|
||||
// unless the app is temporarily allowlisted. A high-priority
|
||||
// FCM message does that; a downgraded one or a UnifiedPush
|
||||
// message may not (BackgroundServiceStartNotAllowedException
|
||||
// on 12+), and uncaught this crashed the app on every such
|
||||
// push. Run the same task from a job instead.
|
||||
releaseStartWakeLock()
|
||||
Log.w(TAG, "Push service start refused in the background, using a job: ${e.message}")
|
||||
BulwarkPushJobService.schedule(context, data)
|
||||
}
|
||||
}
|
||||
|
||||
@Volatile private var startWakeLock: PowerManager.WakeLock? = null
|
||||
|
||||
/** Drops the wake lock taken in [startHeadlessTask]; it also times out on its own. */
|
||||
fun releaseStartWakeLock() {
|
||||
startWakeLock?.release()
|
||||
startWakeLock = null
|
||||
}
|
||||
|
||||
fun ensureChannel(context: Context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.anonymous.bulwarkmobile
|
||||
|
||||
import android.app.job.JobInfo
|
||||
import android.app.job.JobParameters
|
||||
import android.app.job.JobScheduler
|
||||
import android.app.job.JobService
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.PersistableBundle
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import com.facebook.react.ReactApplication
|
||||
import com.facebook.react.ReactInstanceEventListener
|
||||
import com.facebook.react.bridge.ReactContext
|
||||
import com.facebook.react.bridge.UiThreadUtil
|
||||
import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags
|
||||
import com.facebook.react.jstasks.HeadlessJsTaskConfig
|
||||
import com.facebook.react.jstasks.HeadlessJsTaskContext
|
||||
import com.facebook.react.jstasks.HeadlessJsTaskEventListener
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* Runs the BulwarkPushTask headless JS task from a JobScheduler job.
|
||||
*
|
||||
* A push normally starts BulwarkPushTaskService, but Android 8+ only lets a
|
||||
* background app start a service while it is temporarily allowlisted, which a
|
||||
* high-priority FCM message does. FCM delivers some messages at normal
|
||||
* priority anyway (it downgrades apps whose high-priority pushes rarely end in
|
||||
* a notification), and a UnifiedPush distributor may not raise the app at all.
|
||||
* The service start then throws, so the push is handed to this job instead:
|
||||
* expedited on Android 12+, where it runs right away while the app has
|
||||
* expedited-job quota and as a regular job after that, and a regular job on
|
||||
* Android 8-11. It needs no new dependency and ends in the same notification
|
||||
* as the service path.
|
||||
*/
|
||||
class BulwarkPushJobService : JobService(), HeadlessJsTaskEventListener {
|
||||
private class RunningTask(val params: JobParameters, val startedAt: Long)
|
||||
|
||||
private val running = ConcurrentHashMap<Int, RunningTask>()
|
||||
private var taskContext: HeadlessJsTaskContext? = null
|
||||
|
||||
override fun onStartJob(params: JobParameters): Boolean {
|
||||
val config = BulwarkPushTaskService.taskConfig(Bundle(params.extras))
|
||||
withReadyReactContext { startTask(it, config, params) }
|
||||
return true
|
||||
}
|
||||
|
||||
// The JS task has its own 30 s timeout and finishes on its own; nothing to
|
||||
// reschedule if the system stops the job first.
|
||||
override fun onStopJob(params: JobParameters): Boolean {
|
||||
running.values.removeIf { it.params.jobId == params.jobId }
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
taskContext?.removeTaskEventListener(this)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onHeadlessJsTaskStart(taskId: Int) = Unit
|
||||
|
||||
override fun onHeadlessJsTaskFinish(taskId: Int) {
|
||||
val task = running.remove(taskId) ?: return
|
||||
val ms = SystemClock.elapsedRealtime() - task.startedAt
|
||||
Log.i(TAG, "Push task $taskId from job ${task.params.jobId} finished after $ms ms")
|
||||
jobFinished(task.params, false)
|
||||
}
|
||||
|
||||
private fun startTask(context: ReactContext, config: HeadlessJsTaskConfig, params: JobParameters) {
|
||||
val tasks = HeadlessJsTaskContext.getInstance(context)
|
||||
if (taskContext !== tasks) {
|
||||
taskContext?.removeTaskEventListener(this)
|
||||
tasks.addTaskEventListener(this)
|
||||
taskContext = tasks
|
||||
}
|
||||
UiThreadUtil.runOnUiThread {
|
||||
try {
|
||||
val taskId = tasks.startTask(config)
|
||||
running[taskId] = RunningTask(params, SystemClock.elapsedRealtime())
|
||||
} catch (e: IllegalStateException) {
|
||||
// The app came to the foreground since the push arrived and
|
||||
// syncs on its own there.
|
||||
Log.i(TAG, "Push job ${params.jobId} skipped: ${e.message}")
|
||||
jobFinished(params, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls [onReady] once with a React context whose JS instance is up,
|
||||
* starting React Native if the process was woken for this job.
|
||||
*
|
||||
* A context exists before its instance is ready, and a task started on
|
||||
* it then never reaches JS (HeadlessJsTaskContext only logs "CatalystInstance
|
||||
* not available" and waits for the timeout), which happened when several
|
||||
* pushes arrived while the app was starting. So the listener goes in
|
||||
* first and the ready check after it; whichever sees the ready instance
|
||||
* first runs the task.
|
||||
*/
|
||||
private fun withReadyReactContext(onReady: (ReactContext) -> Unit) {
|
||||
val done = AtomicBoolean(false)
|
||||
val runOnce = { context: ReactContext -> if (done.compareAndSet(false, true)) onReady(context) }
|
||||
val app = application as ReactApplication
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
val host = checkNotNull(app.reactHost) { "ReactHost is not initialized in New Architecture" }
|
||||
val listener = object : ReactInstanceEventListener {
|
||||
override fun onReactContextInitialized(context: ReactContext) {
|
||||
host.removeReactInstanceEventListener(this)
|
||||
runOnce(context)
|
||||
}
|
||||
}
|
||||
host.addReactInstanceEventListener(listener)
|
||||
val current = host.currentReactContext
|
||||
if (current != null && current.hasActiveReactInstance()) {
|
||||
host.removeReactInstanceEventListener(listener)
|
||||
runOnce(current)
|
||||
} else {
|
||||
host.start()
|
||||
}
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
val manager = app.reactNativeHost.reactInstanceManager
|
||||
val listener = object : ReactInstanceEventListener {
|
||||
override fun onReactContextInitialized(context: ReactContext) {
|
||||
manager.removeReactInstanceEventListener(this)
|
||||
runOnce(context)
|
||||
}
|
||||
}
|
||||
manager.addReactInstanceEventListener(listener)
|
||||
val current = manager.currentReactContext
|
||||
if (current != null && current.hasActiveReactInstance()) {
|
||||
manager.removeReactInstanceEventListener(listener)
|
||||
runOnce(current)
|
||||
} else if (!manager.hasStartedCreatingInitialContext()) {
|
||||
manager.createReactContextInBackground()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "BulwarkPush"
|
||||
|
||||
// A block of job ids for push jobs, one per push that is waiting or
|
||||
// running. A pending job with the same id would be replaced (and a
|
||||
// running one stopped), so each push takes a free id.
|
||||
private const val JOB_ID_BASE = 0x42554c00
|
||||
private const val JOB_ID_COUNT = 32
|
||||
|
||||
/** Schedules the push task as a job. Returns false if no job could be scheduled. */
|
||||
fun schedule(context: Context, data: Map<String, String>): Boolean = try {
|
||||
val scheduler = context.getSystemService(JobScheduler::class.java)
|
||||
val busy = scheduler.allPendingJobs.mapTo(HashSet()) { it.id }
|
||||
val jobId = (JOB_ID_BASE until JOB_ID_BASE + JOB_ID_COUNT).firstOrNull { it !in busy }
|
||||
if (jobId == null) {
|
||||
// Every slot holds a push that has not finished yet; any of
|
||||
// them fetches the same new mail.
|
||||
Log.w(TAG, "Push not scheduled: $JOB_ID_COUNT push jobs already pending")
|
||||
false
|
||||
} else {
|
||||
val extras = PersistableBundle().apply {
|
||||
for ((key, value) in data) putString(key, value)
|
||||
}
|
||||
val job = JobInfo.Builder(jobId, ComponentName(context, BulwarkPushJobService::class.java))
|
||||
.setExtras(extras)
|
||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||
.apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) setExpedited(true) }
|
||||
.build()
|
||||
val scheduled = scheduler.schedule(job) == JobScheduler.RESULT_SUCCESS
|
||||
Log.i(TAG, "Push job $jobId scheduled: $scheduled")
|
||||
scheduled
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Push job could not be scheduled", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,32 @@
|
||||
package com.anonymous.bulwarkmobile
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.jstasks.HeadlessJsTaskConfig
|
||||
import com.facebook.react.HeadlessJsTaskService
|
||||
|
||||
class BulwarkPushTaskService : HeadlessJsTaskService() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int =
|
||||
super.onStartCommand(intent, flags, startId).also {
|
||||
// startTask has taken React Native's wake lock by now.
|
||||
BulwarkMessagingService.releaseStartWakeLock()
|
||||
}
|
||||
|
||||
override fun getTaskConfig(intent: Intent?): HeadlessJsTaskConfig? {
|
||||
val extras = intent?.extras ?: return null
|
||||
return HeadlessJsTaskConfig(
|
||||
"BulwarkPushTask",
|
||||
Arguments.fromBundle(extras),
|
||||
30000L,
|
||||
false,
|
||||
)
|
||||
return taskConfig(extras)
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Shared with BulwarkPushJobService, which runs the same task when
|
||||
// this service may not be started.
|
||||
fun taskConfig(extras: Bundle): HeadlessJsTaskConfig =
|
||||
HeadlessJsTaskConfig(
|
||||
"BulwarkPushTask",
|
||||
Arguments.fromBundle(extras),
|
||||
30000L,
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.anonymous.bulwarkmobile
|
||||
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.Promise
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
||||
import com.facebook.react.bridge.ReactMethod
|
||||
import org.unifiedpush.android.connector.UnifiedPush
|
||||
|
||||
// JS-facing surface for UnifiedPush distributor management. Events from the
|
||||
// distributor (endpoint changes, incoming messages) arrive through
|
||||
// BulwarkUnifiedPushService, which emits them on the shared device-event
|
||||
// channel.
|
||||
class BulwarkUnifiedPushModule(reactContext: ReactApplicationContext)
|
||||
: ReactContextBaseJavaModule(reactContext) {
|
||||
|
||||
override fun getName(): String = "BulwarkUnifiedPush"
|
||||
|
||||
@ReactMethod
|
||||
fun getDistributors(promise: Promise) {
|
||||
try {
|
||||
val array = Arguments.createArray()
|
||||
for (d in UnifiedPush.getDistributors(reactApplicationContext)) array.pushString(d)
|
||||
promise.resolve(array)
|
||||
} catch (err: Exception) {
|
||||
promise.reject("up_distributors_failed", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun getSavedDistributor(promise: Promise) {
|
||||
try {
|
||||
promise.resolve(UnifiedPush.getSavedDistributor(reactApplicationContext))
|
||||
} catch (err: Exception) {
|
||||
promise.reject("up_distributor_failed", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun getAckDistributor(promise: Promise) {
|
||||
try {
|
||||
promise.resolve(UnifiedPush.getAckDistributor(reactApplicationContext))
|
||||
} catch (err: Exception) {
|
||||
promise.reject("up_distributor_failed", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun saveDistributor(distributor: String, promise: Promise) {
|
||||
try {
|
||||
UnifiedPush.saveDistributor(reactApplicationContext, distributor)
|
||||
promise.resolve(null)
|
||||
} catch (err: Exception) {
|
||||
promise.reject("up_save_distributor_failed", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Asks the saved distributor for an endpoint. The endpoint itself arrives
|
||||
// asynchronously via BulwarkUnifiedPushService.onNewEndpoint (event
|
||||
// `up:newEndpoint`); failures via `up:registrationFailed`. `vapid` is the
|
||||
// relay's VAPID public key - optional, some distributors use it to
|
||||
// restrict who may push.
|
||||
@ReactMethod
|
||||
fun register(vapid: String?, promise: Promise) {
|
||||
try {
|
||||
UnifiedPush.register(
|
||||
reactApplicationContext,
|
||||
messageForDistributor = null,
|
||||
vapid = vapid?.takeIf { it.isNotBlank() },
|
||||
)
|
||||
promise.resolve(null)
|
||||
} catch (err: Exception) {
|
||||
promise.reject("up_register_failed", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun unregister(promise: Promise) {
|
||||
try {
|
||||
UnifiedPush.unregister(reactApplicationContext)
|
||||
BulwarkUnifiedPushService.clearEndpoint(reactApplicationContext)
|
||||
promise.resolve(null)
|
||||
} catch (err: Exception) {
|
||||
promise.reject("up_unregister_failed", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun getEndpoint(promise: Promise) {
|
||||
val stored = BulwarkUnifiedPushService.readEndpoint(reactApplicationContext)
|
||||
if (stored == null) {
|
||||
promise.resolve(null)
|
||||
return
|
||||
}
|
||||
val (url, p256dh, auth) = stored
|
||||
val map = Arguments.createMap().apply {
|
||||
putString("url", url)
|
||||
putString("p256dh", p256dh)
|
||||
putString("auth", auth)
|
||||
}
|
||||
promise.resolve(map)
|
||||
}
|
||||
|
||||
// NativeEventEmitter required no-ops.
|
||||
@ReactMethod
|
||||
fun addListener(eventName: String) {}
|
||||
|
||||
@ReactMethod
|
||||
fun removeListeners(count: Int) {}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.anonymous.bulwarkmobile
|
||||
|
||||
import android.content.Context
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import org.unifiedpush.android.connector.FailedReason
|
||||
import org.unifiedpush.android.connector.PushService
|
||||
import org.unifiedpush.android.connector.data.PushEndpoint
|
||||
import org.unifiedpush.android.connector.data.PushMessage
|
||||
|
||||
// Receives UnifiedPush events from the user's distributor app (ntfy, ...).
|
||||
// The relay sends the same content-blind ForwardPayload it sends over FCM -
|
||||
// as encrypted Web Push when the connector provided RFC 8291 keys (the
|
||||
// connector decrypts before this service sees it) - so an incoming message is
|
||||
// dispatched exactly like an FCM data message: headless task when the app is
|
||||
// backgrounded, `fcm:message` device event for the live JS instance.
|
||||
class BulwarkUnifiedPushService : PushService() {
|
||||
|
||||
override fun onNewEndpoint(endpoint: PushEndpoint, instance: String) {
|
||||
saveEndpoint(this, endpoint)
|
||||
val params = Arguments.createMap().apply {
|
||||
putString("url", endpoint.url)
|
||||
putString("p256dh", endpoint.pubKeySet?.pubKey)
|
||||
putString("auth", endpoint.pubKeySet?.auth)
|
||||
}
|
||||
BulwarkFcmModule.emit("up:newEndpoint", params)
|
||||
}
|
||||
|
||||
override fun onMessage(message: PushMessage, instance: String) {
|
||||
BulwarkMessagingService.ensureChannel(this)
|
||||
val data = parsePayload(message.content) ?: return
|
||||
|
||||
if (!BulwarkMessagingService.isAppInForeground()) {
|
||||
BulwarkMessagingService.startHeadlessTask(applicationContext, data)
|
||||
}
|
||||
|
||||
// Same event name and shape as the FCM path so the foreground JS
|
||||
// handler (App.tsx addMessageListener) works transport-agnostically.
|
||||
val params = Arguments.createMap().apply {
|
||||
val dataMap = Arguments.createMap()
|
||||
for ((k, v) in data) dataMap.putString(k, v)
|
||||
putMap("data", dataMap)
|
||||
}
|
||||
BulwarkFcmModule.emit("fcm:message", params)
|
||||
}
|
||||
|
||||
override fun onUnregistered(instance: String) {
|
||||
clearEndpoint(this)
|
||||
BulwarkFcmModule.emit("up:unregistered", Arguments.createMap())
|
||||
}
|
||||
|
||||
override fun onRegistrationFailed(reason: FailedReason, instance: String) {
|
||||
val params = Arguments.createMap().apply { putString("reason", reason.name) }
|
||||
BulwarkFcmModule.emit("up:registrationFailed", params)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREFS = "bulwark_unifiedpush"
|
||||
private const val KEY_URL = "endpointUrl"
|
||||
private const val KEY_P256DH = "endpointP256dh"
|
||||
private const val KEY_AUTH = "endpointAuth"
|
||||
|
||||
// The endpoint arrives asynchronously (possibly while no JS instance
|
||||
// is alive), so persist it for BulwarkUnifiedPushModule.getEndpoint.
|
||||
fun saveEndpoint(context: Context, endpoint: PushEndpoint) {
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit()
|
||||
.putString(KEY_URL, endpoint.url)
|
||||
.putString(KEY_P256DH, endpoint.pubKeySet?.pubKey)
|
||||
.putString(KEY_AUTH, endpoint.pubKeySet?.auth)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun readEndpoint(context: Context): Triple<String, String?, String?>? {
|
||||
val prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
val url = prefs.getString(KEY_URL, null) ?: return null
|
||||
return Triple(url, prefs.getString(KEY_P256DH, null), prefs.getString(KEY_AUTH, null))
|
||||
}
|
||||
|
||||
fun clearEndpoint(context: Context) {
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit().clear().apply()
|
||||
}
|
||||
|
||||
// The relay's ForwardPayload JSON, flattened to the string map the
|
||||
// FCM data payload uses (repos/relay/src/fcm.ts) so the headless task
|
||||
// and foreground handler can stay transport-blind.
|
||||
fun parsePayload(content: ByteArray): Map<String, String>? = try {
|
||||
val json = JSONObject(String(content, Charsets.UTF_8))
|
||||
buildMap {
|
||||
json.optString("kind").takeIf { it.isNotEmpty() }?.let { put("kind", it) }
|
||||
json.optString("accountLabel").takeIf { it.isNotEmpty() }?.let { put("accountLabel", it) }
|
||||
json.optString("accountId").takeIf { it.isNotEmpty() }?.let { put("accountId", it) }
|
||||
put("emailIds", (json.optJSONArray("emailIds") ?: JSONArray()).toString())
|
||||
put("changed", (json.optJSONObject("changed") ?: JSONObject()).toString())
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,15 +17,17 @@ class MainActivity : ReactActivity() {
|
||||
// coloring the background, status bar, and navigation bar.
|
||||
// This is required for expo-splash-screen.
|
||||
setTheme(R.style.AppTheme);
|
||||
ShareIntentStore.rewriteSendToAsView(intent)
|
||||
super.onCreate(null)
|
||||
NotificationTapStore.captureFromIntent(intent)
|
||||
ShareIntentStore.captureFromIntent(intent)
|
||||
ShareIntentStore.captureFromIntent(intent, contentResolver)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
ShareIntentStore.rewriteSendToAsView(intent)
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
ShareIntentStore.captureFromIntent(intent)?.let { share ->
|
||||
ShareIntentStore.captureFromIntent(intent, contentResolver)?.let { share ->
|
||||
BulwarkFcmModule.emit("app:share", share.toMap())
|
||||
}
|
||||
val payload = NotificationTapStore.captureFromIntent(intent) ?: return
|
||||
|
||||
@@ -40,13 +40,17 @@ object NotificationTapStore {
|
||||
// the navigation payload.
|
||||
val subject = extras.getString(EXTRA_SUBJECT)?.take(512)
|
||||
val accountId = extras.getString(EXTRA_ACCOUNT_ID)?.takeIf { ACCOUNT_ID_PATTERN.matches(it) }
|
||||
val payload = TapPayload(emailId, threadId, subject, accountId)
|
||||
// The JMAP account a group/shared mailbox's message lives in - a JMAP
|
||||
// id like the email id. Optional: older notifications don't carry it.
|
||||
val jmapAccountId = extras.getString(EXTRA_JMAP_ACCOUNT_ID)?.takeIf { ID_PATTERN.matches(it) }
|
||||
val payload = TapPayload(emailId, threadId, subject, accountId, jmapAccountId)
|
||||
pending = payload
|
||||
// Clear so a subsequent activity lifecycle event doesn't replay this.
|
||||
extras.remove(EXTRA_EMAIL_ID)
|
||||
extras.remove(EXTRA_THREAD_ID)
|
||||
extras.remove(EXTRA_SUBJECT)
|
||||
extras.remove(EXTRA_ACCOUNT_ID)
|
||||
extras.remove(EXTRA_JMAP_ACCOUNT_ID)
|
||||
return payload
|
||||
}
|
||||
|
||||
@@ -55,12 +59,14 @@ object NotificationTapStore {
|
||||
val threadId: String,
|
||||
val subject: String?,
|
||||
val accountId: String?,
|
||||
val jmapAccountId: String?,
|
||||
) {
|
||||
fun toMap(): WritableMap = Arguments.createMap().apply {
|
||||
putString("emailId", emailId)
|
||||
putString("threadId", threadId)
|
||||
if (subject != null) putString("subject", subject)
|
||||
if (accountId != null) putString("accountId", accountId)
|
||||
if (jmapAccountId != null) putString("jmapAccountId", jmapAccountId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,4 +74,5 @@ object NotificationTapStore {
|
||||
const val EXTRA_THREAD_ID = "bulwark.notification.threadId"
|
||||
const val EXTRA_SUBJECT = "bulwark.notification.subject"
|
||||
const val EXTRA_ACCOUNT_ID = "bulwark.notification.accountId"
|
||||
const val EXTRA_JMAP_ACCOUNT_ID = "bulwark.notification.jmapAccountId"
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package com.anonymous.bulwarkmobile
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.OpenableColumns
|
||||
import android.webkit.MimeTypeMap
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
|
||||
// Process-wide holder for the most recent ACTION_SEND / ACTION_SEND_MULTIPLE
|
||||
// intent (the system share sheet). MainActivity fills this on onCreate /
|
||||
// onNewIntent; JS drains it via BulwarkFcm.getInitialShare or reacts to the
|
||||
// live "app:share" event and opens the composer.
|
||||
// live "app:share" event and opens the composer. `SENDTO mailto:` intents are
|
||||
// rewritten into `VIEW mailto:` links instead (see rewriteSendToAsView).
|
||||
object ShareIntentStore {
|
||||
@Volatile
|
||||
private var pending: SharePayload? = null
|
||||
@@ -20,7 +24,7 @@ object ShareIntentStore {
|
||||
return value
|
||||
}
|
||||
|
||||
fun captureFromIntent(intent: Intent?): SharePayload? {
|
||||
fun captureFromIntent(intent: Intent?, resolver: ContentResolver): SharePayload? {
|
||||
val action = intent?.action ?: return null
|
||||
if (action != Intent.ACTION_SEND && action != Intent.ACTION_SEND_MULTIPLE) return null
|
||||
|
||||
@@ -35,11 +39,14 @@ object ShareIntentStore {
|
||||
val type = intent.type
|
||||
|
||||
if (text.isNullOrBlank() && uris.isEmpty()) return null
|
||||
val files = uris.mapIndexed { i, uri -> describe(resolver, uri, type, i) }
|
||||
val payload = SharePayload(
|
||||
text = text,
|
||||
subject = subject,
|
||||
uris = uris.map { it.toString() },
|
||||
mimeTypes = uris.map { type ?: "" },
|
||||
mimeTypes = files.map { it.mimeType },
|
||||
names = files.map { it.name },
|
||||
sizes = files.map { it.size },
|
||||
)
|
||||
pending = payload
|
||||
// Clear so a later lifecycle event doesn't replay this share.
|
||||
@@ -50,6 +57,80 @@ object ShareIntentStore {
|
||||
return payload
|
||||
}
|
||||
|
||||
// `SENDTO mailto:` is how most apps start an email, but React Native's
|
||||
// Linking only forwards VIEW intents, so the app opened on the inbox.
|
||||
// Turn it into the equivalent `VIEW mailto:` link, folding the recipient,
|
||||
// subject and text extras into the URI, so it reaches JS like any other
|
||||
// mailto link: Linking.getInitialURL on a cold start, the "url" event
|
||||
// while running. Must run before the intent is handed to React Native.
|
||||
fun rewriteSendToAsView(intent: Intent?) {
|
||||
val action = intent?.action ?: return
|
||||
if (action != Intent.ACTION_SENDTO) return
|
||||
val data = intent.data ?: return
|
||||
if (!"mailto".equals(data.scheme, ignoreCase = true)) return
|
||||
|
||||
val params = mutableListOf<String>()
|
||||
addressExtra(intent, Intent.EXTRA_EMAIL).forEach { params.add("to=${Uri.encode(it)}") }
|
||||
addressExtra(intent, Intent.EXTRA_CC).forEach { params.add("cc=${Uri.encode(it)}") }
|
||||
addressExtra(intent, Intent.EXTRA_BCC).forEach { params.add("bcc=${Uri.encode(it)}") }
|
||||
intent.getCharSequenceExtra(Intent.EXTRA_SUBJECT)?.let {
|
||||
params.add("subject=${Uri.encode(it.toString().take(512))}")
|
||||
}
|
||||
intent.getCharSequenceExtra(Intent.EXTRA_TEXT)?.let {
|
||||
params.add("body=${Uri.encode(it.toString().take(MAX_TEXT))}")
|
||||
}
|
||||
|
||||
val link = data.toString()
|
||||
val separator = if (link.contains('?')) "&" else "?"
|
||||
intent.action = Intent.ACTION_VIEW
|
||||
intent.data = if (params.isEmpty()) data else Uri.parse(link + separator + params.joinToString("&"))
|
||||
}
|
||||
|
||||
// EXTRA_EMAIL/CC/BCC are documented as String[], but some apps put a
|
||||
// single String.
|
||||
private fun addressExtra(intent: Intent, key: String): List<String> =
|
||||
intent.getStringArrayExtra(key)?.filterNotNull() ?: listOfNotNull(intent.getStringExtra(key))
|
||||
|
||||
private class SharedFile(val name: String, val mimeType: String, val size: Double)
|
||||
|
||||
// Ask the provider for the file's real name, type and size: a MediaStore
|
||||
// URI ends in a bare row id ("1000000027") and SEND_MULTIPLE only carries
|
||||
// a wildcard type such as "image/*". Size is -1 when unknown.
|
||||
private fun describe(resolver: ContentResolver, uri: Uri, intentType: String?, index: Int): SharedFile {
|
||||
var displayName: String? = null
|
||||
var size = -1.0
|
||||
try {
|
||||
resolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE), null, null, null)?.use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
val nameColumn = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
if (nameColumn >= 0 && !cursor.isNull(nameColumn)) displayName = cursor.getString(nameColumn)
|
||||
val sizeColumn = cursor.getColumnIndex(OpenableColumns.SIZE)
|
||||
if (sizeColumn >= 0 && !cursor.isNull(sizeColumn)) size = cursor.getLong(sizeColumn).toDouble()
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
// Provider without OpenableColumns: fall back to the URI below.
|
||||
}
|
||||
val resolvedType = try {
|
||||
resolver.getType(uri)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
val mimeType = resolvedType ?: intentType?.takeUnless { it.contains('*') } ?: ""
|
||||
val name = displayName?.takeIf { it.isNotBlank() } ?: fallbackName(uri, mimeType, index)
|
||||
return SharedFile(name, mimeType, size)
|
||||
}
|
||||
|
||||
// Last path segment plus an extension for the MIME type, so the
|
||||
// recipient's mail client can still open the attachment.
|
||||
private fun fallbackName(uri: Uri, mimeType: String, index: Int): String {
|
||||
val base = uri.lastPathSegment?.substringAfterLast('/')?.takeIf { it.isNotBlank() }
|
||||
?: "shared-${index + 1}"
|
||||
if (base.contains('.')) return base
|
||||
val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)
|
||||
return if (extension.isNullOrEmpty()) base else "$base.$extension"
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun streamExtra(intent: Intent): Uri? =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
@@ -71,12 +152,16 @@ object ShareIntentStore {
|
||||
val subject: String?,
|
||||
val uris: List<String>,
|
||||
val mimeTypes: List<String>,
|
||||
val names: List<String>,
|
||||
val sizes: List<Double>,
|
||||
) {
|
||||
fun toMap(): WritableMap = Arguments.createMap().apply {
|
||||
if (text != null) putString("text", text)
|
||||
if (subject != null) putString("subject", subject)
|
||||
putArray("uris", Arguments.fromList(uris))
|
||||
putArray("mimeTypes", Arguments.fromList(mimeTypes))
|
||||
putArray("names", Arguments.fromList(names))
|
||||
putArray("sizes", Arguments.fromList(sizes))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Status-bar icon for mail notifications: the Bulwark shield from
|
||||
assets/logos/Bulwark Logo White.svg, white on transparent. Android draws
|
||||
only the alpha channel of a small icon; the group scale keeps the shield
|
||||
inside the 22dp live area of the 24dp canvas. -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="1000"
|
||||
android:viewportHeight="1000">
|
||||
<group
|
||||
android:pivotX="500"
|
||||
android:pivotY="500"
|
||||
android:scaleX="0.9"
|
||||
android:scaleY="0.9">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M 373.467 557.247 L 55.226 271.52 L 55.226 385.564 C 55.226 398.153 62.453 415.6 71.355 424.501 L 107.525 460.671 C 116.426 469.573 123.653 487.02 123.653 499.609 L 123.653 682.08 C 123.655 718.462 133.336 750.017 152.047 778.668 L 373.467 557.247 Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M 626.533 557.247 L 944.774 271.52 L 944.774 396.969 C 944.774 403.263 941.16 411.987 936.709 416.437 L 884.411 468.736 C 879.96 473.186 876.347 481.91 876.347 488.204 L 876.347 682.08 C 876.345 718.462 866.664 750.017 847.953 778.668 L 626.533 557.247 Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M 500 638.743 L 500 638.743 C 466.206 638.738 448.855 625.316 424.448 603.033 L 197.809 829.672 C 261.382 884.635 364.213 931.859 500 990 Q 500 990 500 990 C 635.787 931.859 738.618 884.635 802.191 829.672 L 575.552 603.033 C 551.145 625.316 533.794 638.738 500 638.743 Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M 483.028 563.647 L 63.713 187.183 C 59.029 182.978 55.226 174.454 55.226 168.159 L 55.226 122.542 C 55.226 116.247 60.206 109.988 66.339 108.573 L 215.181 74.225 C 221.314 72.809 226.293 76.77 226.293 83.065 L 226.293 176.92 L 352.034 147.895 C 358.167 146.479 363.147 140.219 363.147 133.925 L 363.147 51.483 C 363.147 45.189 368.126 38.93 374.259 37.514 L 488.888 11.061 C 495.021 9.646 504.979 9.646 511.112 11.061 L 625.741 37.514 C 631.874 38.93 636.853 45.189 636.853 51.483 L 636.853 133.925 C 636.853 140.219 641.833 146.479 647.966 147.895 L 773.707 176.92 L 773.707 83.065 C 773.707 76.77 778.686 72.809 784.819 74.225 L 933.661 108.573 C 939.794 109.988 944.774 116.247 944.774 122.542 L 944.774 168.159 C 944.774 174.454 940.971 182.978 936.287 187.183 L 516.972 563.647 C 507.605 572.056 492.395 572.056 483.028 563.647 Z" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -1 +1,6 @@
|
||||
<resources/>
|
||||
<resources>
|
||||
<!-- As `splash.dark.backgroundColor` in app.config.js. -->
|
||||
<color name="splashscreen_background">#09090b</color>
|
||||
<!-- Matches the dark palette background (src/theme/tokens.ts). -->
|
||||
<color name="window_background">#09090b</color>
|
||||
</resources>
|
||||
@@ -1,6 +1,9 @@
|
||||
<resources>
|
||||
<color name="splashscreen_background">#09090b</color>
|
||||
<!-- Splash background, as `splash.backgroundColor` in app.config.js; the dark one is in values-night. -->
|
||||
<color name="splashscreen_background">#ffffff</color>
|
||||
<color name="iconBackground">#09090b</color>
|
||||
<color name="colorPrimary">#023c69</color>
|
||||
<color name="colorPrimaryDark">#09090b</color>
|
||||
<!-- Behind the React root until the first frame; matches the light palette background (src/theme/tokens.ts). -->
|
||||
<color name="window_background">#ffffff</color>
|
||||
</resources>
|
||||
@@ -4,6 +4,7 @@
|
||||
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="android:statusBarColor">#09090b</item>
|
||||
<item name="android:windowBackground">@color/window_background</item>
|
||||
</style>
|
||||
<style name="Theme.App.SplashScreen" parent="AppTheme">
|
||||
<item name="android:windowBackground">@drawable/ic_launcher_background</item>
|
||||
|
||||
@@ -25,6 +25,11 @@ android.useAndroidX=true
|
||||
# Enable AAPT2 PNG crunching
|
||||
android.enablePngCrunchInReleaseBuilds=true
|
||||
|
||||
# Shrink release builds with R8 (code) and the resource shrinker. Keep rules
|
||||
# for classes reached by reflection go in app/proguard-rules.pro.
|
||||
android.enableMinifyInReleaseBuilds=true
|
||||
android.enableShrinkResourcesInReleaseBuilds=true
|
||||
|
||||
# Use this property to specify which architecture you want to build.
|
||||
# You can also override it from the CLI using
|
||||
# ./gradlew <task> -PreactNativeArchitectures=x86_64
|
||||
@@ -63,3 +68,9 @@ expo.useLegacyPackaging=false
|
||||
# Specifies whether the app is configured to use edge-to-edge via the app config or plugin
|
||||
# WARNING: This property has been deprecated and will be removed in Expo SDK 55. Use `edgeToEdgeEnabled` or `react.edgeToEdgeEnabled` to determine whether the project is using edge-to-edge.
|
||||
expo.edgeToEdgeEnabled=true
|
||||
|
||||
# AsyncStorage caps its SQLite database at 6 MB unless this is set. The
|
||||
# offline mail cache keeps message bodies there and can be set to 500 MB,
|
||||
# and once the database is full every write fails, the account registry
|
||||
# and the outbox included. This is a ceiling, not an allocation.
|
||||
AsyncStorage_db_size_in_MB=1024
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
const path = require('node:path');
|
||||
|
||||
// babel-preset-expo ships inside `expo` rather than as a direct dependency, so
|
||||
// resolve it from there; this is the preset Expo uses when no config exists.
|
||||
const presetExpo = require.resolve('babel-preset-expo', {
|
||||
paths: [path.dirname(require.resolve('expo/package.json'))],
|
||||
});
|
||||
|
||||
module.exports = function babelConfig(api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: [presetExpo],
|
||||
plugins: [require.resolve('./scripts/babel-plugin-lucide-imports')],
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,294 @@
|
||||
# Android release signing
|
||||
|
||||
The release APK is built by
|
||||
[.github/workflows/release-android.yml](../.github/workflows/release-android.yml)
|
||||
on an Ubuntu runner whenever a GitHub release is published, and attached to that
|
||||
release. Installed apps pick it up through the in-app updater.
|
||||
|
||||
Every APK up to and including 0.1.62 was signed with `android/app/debug.keystore`,
|
||||
the debug key that ships with the React Native template. Its password is
|
||||
`android` and copies of it are everywhere, so anyone could sign an APK that
|
||||
Android accepts as an update to Bulwark Mobile. Such an "update" runs as the
|
||||
app, with its accounts, SecureStore credentials and mail cache. This is
|
||||
[S2 in the 2026-09 audit](audit-2026-09.md#s2).
|
||||
|
||||
Release builds now need a real key:
|
||||
|
||||
- `android/app/build.gradle` reads the key from `BULWARK_RELEASE_*` properties.
|
||||
Without them, a release build fails in `checkReleaseSigning` before anything
|
||||
is packaged. Debug builds and `npx expo run:android` are not affected.
|
||||
- The workflow fails in its first step when a signing secret is missing, checks
|
||||
the key before the build, and refuses to upload an APK that Android 9+ would
|
||||
not see as signed by the release key.
|
||||
- `versionCode` now comes from `VERSION` (see [versionCode](#versioncode)).
|
||||
|
||||
## One-time setup
|
||||
|
||||
The commands are for Git Bash on Windows, run from the repository root. They
|
||||
need `keytool`, which ships with every JDK, and `apksigner` from the Android SDK
|
||||
build-tools. Its `.jar` runs on Java 8 or newer:
|
||||
|
||||
```bash
|
||||
apksigner() { java -jar "$LOCALAPPDATA/Android/Sdk/build-tools/36.0.0/lib/apksigner.jar" "$@"; }
|
||||
```
|
||||
|
||||
On macOS or Linux, call `$ANDROID_HOME/build-tools/36.0.0/apksigner` instead.
|
||||
|
||||
### 1. Generate the release key
|
||||
|
||||
```bash
|
||||
read -rsp 'Keystore password: ' BULWARK_KS_PASS; echo; export BULWARK_KS_PASS
|
||||
keytool -genkeypair -v -storetype PKCS12 -keystore bulwark-release.p12 \
|
||||
-alias bulwark -keyalg RSA -keysize 4096 -validity 10000 \
|
||||
-dname "CN=Bulwark Mail, O=Bulwark Mail" \
|
||||
-storepass:env BULWARK_KS_PASS -keypass:env BULWARK_KS_PASS
|
||||
```
|
||||
|
||||
`-validity 10000` is about 27 years. A PKCS12 keystore uses one password for
|
||||
the store and the key, so `ANDROID_KEYSTORE_PASSWORD` and `ANDROID_KEY_PASSWORD`
|
||||
below get the same value. `*.p12` is gitignored, but move the file out of the
|
||||
checkout once the secrets are set.
|
||||
|
||||
**Back up `bulwark-release.p12` and its password in two places**, for example
|
||||
a password manager and an offline copy. If the key is lost, no later APK can
|
||||
update an installed app: every user has to uninstall, which loses their accounts
|
||||
and local data, and install again. If the key leaks, you are back where S2
|
||||
started.
|
||||
|
||||
The certificate's SHA-256 is what the workflow log prints as the release key:
|
||||
|
||||
```bash
|
||||
keytool -exportcert -keystore bulwark-release.p12 -alias bulwark \
|
||||
-storepass:env BULWARK_KS_PASS | sha256sum
|
||||
```
|
||||
|
||||
### 2. Create the rotation lineage
|
||||
|
||||
Skip this and the first release signed with the new key cannot be installed
|
||||
over any existing install. Android reports "App not installed as package
|
||||
conflicts with an existing package", and every user has to uninstall and
|
||||
reinstall.
|
||||
|
||||
APK Signature Scheme v3, read by Android 9 and newer, lets an APK carry a
|
||||
*signing lineage*: a record signed by the old key that names the new key as its
|
||||
successor. The old key here is the public debug key, so you can create that
|
||||
record yourself:
|
||||
|
||||
```bash
|
||||
apksigner rotate --out bulwark.lineage \
|
||||
--old-signer --ks android/app/debug.keystore --ks-key-alias androiddebugkey \
|
||||
--ks-pass pass:android --key-pass pass:android \
|
||||
--set-installed-data true --set-rollback false \
|
||||
--set-permission false --set-auth false --set-shared-uid false \
|
||||
--new-signer --ks bulwark-release.p12 --ks-key-alias bulwark \
|
||||
--ks-pass env:BULWARK_KS_PASS --key-pass env:BULWARK_KS_PASS
|
||||
apksigner lineage --in bulwark.lineage --print-certs
|
||||
```
|
||||
|
||||
The `--set-*` flags on the old signer decide what the debug key can still do on
|
||||
a device that has moved to the new key:
|
||||
|
||||
- `--set-installed-data true` lets the new key take over an install made with
|
||||
the debug key. Without it the rotation does nothing.
|
||||
- `--set-rollback false` keeps an APK signed with the debug key from being
|
||||
accepted again afterwards. With `true` the rotation would be pointless, and the
|
||||
workflow refuses such a lineage.
|
||||
- `--set-permission false` stops granting the app's signature permissions to
|
||||
apps signed with the debug key, which includes every React Native debug build.
|
||||
The merged manifest defines one,
|
||||
`com.anonymous.bulwarkmobile.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION`. It
|
||||
guards the receivers that are registered as not exported on Android 12L and
|
||||
older.
|
||||
- `--set-auth false` and `--set-shared-uid false`: the app uses neither.
|
||||
|
||||
The lineage holds only certificates and a signature made with the debug key, so
|
||||
it is not secret. It is stored as a secret only so the workflow can find it.
|
||||
|
||||
### 3. Repository secrets
|
||||
|
||||
Settings → Secrets and variables → Actions:
|
||||
|
||||
| Secret | Value |
|
||||
| --- | --- |
|
||||
| `ANDROID_KEYSTORE_BASE64` | `bulwark-release.p12`, base64-encoded |
|
||||
| `ANDROID_KEYSTORE_PASSWORD` | The keystore password |
|
||||
| `ANDROID_KEY_ALIAS` | `bulwark` |
|
||||
| `ANDROID_KEY_PASSWORD` | The same password again |
|
||||
| `ANDROID_SIGNING_LINEAGE_BASE64` | `bulwark.lineage`, base64-encoded. Optional, but see step 2 |
|
||||
|
||||
With the GitHub CLI, from the checkout and in the same shell as step 1:
|
||||
|
||||
```bash
|
||||
base64 -w0 bulwark-release.p12 | gh secret set ANDROID_KEYSTORE_BASE64
|
||||
printf %s "$BULWARK_KS_PASS" | gh secret set ANDROID_KEYSTORE_PASSWORD
|
||||
printf %s "$BULWARK_KS_PASS" | gh secret set ANDROID_KEY_PASSWORD
|
||||
gh secret set ANDROID_KEY_ALIAS --body bulwark
|
||||
base64 -w0 bulwark.lineage | gh secret set ANDROID_SIGNING_LINEAGE_BASE64
|
||||
```
|
||||
|
||||
Or copy a file as base64 for the web form:
|
||||
|
||||
```powershell
|
||||
[Convert]::ToBase64String([IO.File]::ReadAllBytes('bulwark-release.p12')) | Set-Clipboard
|
||||
```
|
||||
|
||||
## The first release with the new key
|
||||
|
||||
1. Set all five secrets.
|
||||
2. Release as usual with `npm run bump`.
|
||||
3. In the workflow run, "Verify the APK signature" prints the release key's
|
||||
SHA-256 as the Android 9+ signer and `fac61745…3b9c`, the debug key, as the
|
||||
Android 7-8.1 signer, followed by a warning about the latter. The warning is
|
||||
expected for as long as the lineage is used.
|
||||
4. Check it on the emulators before you announce anything:
|
||||
- Install 0.1.62 (`gh release download 0.1.62 -p '*.apk'`) on
|
||||
`Pixel_6_Pro_API_32` and `Pixel_7_API_34` and sign in. Then `adb install -r`
|
||||
the new APK. It has to install over 0.1.62 and keep the account.
|
||||
- Set `VERSION` above the new release locally (don't commit it), build with
|
||||
`-PallowDebugSignedRelease=true` (see [Local release builds](#local-release-builds))
|
||||
and `adb install -r` that APK over the updated app. It has to fail with
|
||||
`INSTALL_FAILED_UPDATE_INCOMPATIBLE`.
|
||||
5. Add a line `severity: security` to the release notes (web UI or
|
||||
`gh release edit`). The in-app update banner then cannot be dismissed, which
|
||||
matters because a device on 0.1.62 accepts a debug-signed "update" until it
|
||||
has installed this release.
|
||||
|
||||
## What rotation does and does not protect
|
||||
|
||||
| Device | After it installs a rotated release |
|
||||
| --- | --- |
|
||||
| Android 9 and newer (API 28+) | Updates in place and keeps accounts and data. From then on it accepts only updates signed with the release key. An APK signed with the debug key is refused, and so is one that carries a lineage from the debug key to some other key. |
|
||||
| Android 7.0-8.1 (API 24-27) | Updates in place, but these versions do not read v3 signatures. They check the v2 signature, which the workflow still makes with the debug key so that they can update at all. They keep accepting debug-signed "updates". |
|
||||
|
||||
What it does not cover:
|
||||
|
||||
- **Devices that have not updated yet.** Until a device installs a rotated
|
||||
release it trusts the debug key as before. Whoever gets a debug-signed APK onto
|
||||
it first can attach a lineage to their own key and keep the app for good. The
|
||||
window closes for each device when it updates, which is why the release should
|
||||
carry `severity: security`.
|
||||
- **Android 7.0-8.1**, as long as the lineage is used. New installs on those
|
||||
versions also start out trusting the debug key. The only fix is
|
||||
[ending the migration](#ending-the-migration), after which they need a
|
||||
reinstall.
|
||||
- **Anything that already happened.** Rotation does not repair a device that
|
||||
already runs a malicious "update".
|
||||
- **The release key itself.** Rotation moves the trust to
|
||||
`bulwark-release.p12`. It is only as safe as that file and its password.
|
||||
|
||||
The workflow passes `--rotation-min-sdk-version 28` to `apksigner sign`. By
|
||||
default apksigner puts the rotated key in a v3.1 block that only Android 13+
|
||||
reads and signs the v3.0 block with the old key, which would leave Android
|
||||
9-12L on the debug key.
|
||||
|
||||
If you restricted the Firebase API key in `google-services.json` to Android apps
|
||||
by certificate fingerprint in the Google Cloud console, add the release key's
|
||||
SHA-1 there before the first release and keep the debug key's entry until the
|
||||
migration has ended. Otherwise push registration can fail. An unrestricted key
|
||||
needs nothing.
|
||||
|
||||
## Ending the migration
|
||||
|
||||
Keep `ANDROID_SIGNING_LINEAGE_BASE64` set while installs of 0.1.62 or older may
|
||||
still be around: without it they cannot update at all.
|
||||
|
||||
Once you delete it, every APK is signed with the release key alone:
|
||||
|
||||
- Devices that already moved to the release key keep updating normally.
|
||||
- Anything still on 0.1.62 or older, and every Android 7.0-8.1 install, refuses
|
||||
the update. Those users have to uninstall, reinstall and sign in again, and
|
||||
they lose local data. Afterwards their devices trust only the release key.
|
||||
|
||||
## Local release builds
|
||||
|
||||
CI signs releases, so you normally don't need the release key on your machine.
|
||||
For a local release build, for profiling or checking R8, opt in to the debug key:
|
||||
|
||||
```bash
|
||||
cd android
|
||||
./gradlew assembleRelease -PallowDebugSignedRelease=true
|
||||
```
|
||||
|
||||
```powershell
|
||||
$env:ORG_GRADLE_PROJECT_allowDebugSignedRelease = 'true'
|
||||
npx expo run:android --variant release
|
||||
```
|
||||
|
||||
The build prints a warning. The APK is signed with the public key, so never
|
||||
give it to anyone. On a device that has moved to the release key it will not
|
||||
install over the official app; uninstall that first. `allowDebugSignedRelease`
|
||||
is ignored when the `CI` environment variable is set, so the workflow cannot
|
||||
use it.
|
||||
|
||||
To sign locally with the real key, set these as `-P` flags, in
|
||||
`~/.gradle/gradle.properties` or as environment variables. Never put them in
|
||||
`android/gradle.properties`, which is committed.
|
||||
|
||||
```properties
|
||||
BULWARK_RELEASE_STORE_FILE=C:/Users/you/keys/bulwark-release.p12
|
||||
BULWARK_RELEASE_STORE_PASSWORD=...
|
||||
BULWARK_RELEASE_KEY_ALIAS=bulwark
|
||||
BULWARK_RELEASE_KEY_PASSWORD=...
|
||||
```
|
||||
|
||||
That APK has no lineage, so it will not install over a debug-signed install
|
||||
either.
|
||||
|
||||
With neither, a release build stops and lists what is missing. Debug builds and
|
||||
`npx expo run:android` never need any of this.
|
||||
|
||||
## Stack traces
|
||||
|
||||
Release builds are shrunk and obfuscated by R8, so library frames in a crash
|
||||
report look like `com.facebook.react.uimanager.U.a`. The app's own classes
|
||||
(`com.anonymous.bulwarkmobile`) keep their names. The workflow attaches
|
||||
`bulwark-mobile-<version>-<commit>-mapping.txt` to the release next to the APK
|
||||
and keeps it as a workflow artifact. Decode a trace with the mapping of that
|
||||
exact build:
|
||||
|
||||
```bash
|
||||
retrace bulwark-mobile-0.1.63-abc1234-mapping.txt stacktrace.txt
|
||||
```
|
||||
|
||||
`retrace` is in the Android SDK command-line tools (`cmdline-tools/latest/bin`).
|
||||
A local build writes its mapping to
|
||||
`android/app/build/outputs/mapping/release/mapping.txt`.
|
||||
|
||||
## versionCode
|
||||
|
||||
`versionCode` was `1` for every release up to 0.1.62. It is now derived from
|
||||
`VERSION` in `android/app/build.gradle`:
|
||||
|
||||
```
|
||||
major * 10000000 + minor * 10000 + patch 0.1.63 -> 10063
|
||||
```
|
||||
|
||||
Android refuses an update whose `versionCode` is lower than the installed one,
|
||||
so the number may only grow. The formula keeps growing while patch stays below
|
||||
10000 and minor below 1000, and `npm run bump` refuses to go past either. It
|
||||
stays under Android's limit of 2100000000 up to major 209. Existing installs
|
||||
have `versionCode` 1, so the first release with this change updates them
|
||||
normally.
|
||||
|
||||
Never switch to a formula that gives smaller numbers: installed apps would
|
||||
refuse every update until the numbers caught up.
|
||||
|
||||
## Rotating again
|
||||
|
||||
If the release key ever has to change, extend the lineage instead of starting
|
||||
a new one. Generate `bulwark-release-2.p12` as in step 1, with its password in
|
||||
`NEW_KS_PASS`:
|
||||
|
||||
```bash
|
||||
apksigner rotate --in bulwark.lineage --out bulwark-2.lineage \
|
||||
--old-signer --ks bulwark-release.p12 --ks-key-alias bulwark \
|
||||
--ks-pass env:BULWARK_KS_PASS --key-pass env:BULWARK_KS_PASS --set-rollback false \
|
||||
--new-signer --ks bulwark-release-2.p12 --ks-key-alias bulwark \
|
||||
--ks-pass env:NEW_KS_PASS --key-pass env:NEW_KS_PASS
|
||||
```
|
||||
|
||||
Then update the secrets. The workflow accepts any lineage that starts at the
|
||||
debug key and ends at the key in `ANDROID_KEYSTORE_BASE64`, and refuses one in
|
||||
which an earlier key has the rollback capability. As with the first rotation,
|
||||
only Android 9+ devices that install the new release follow it. If the old key
|
||||
leaked, a device that someone reached first stays theirs.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -54,7 +54,7 @@ RN covers the happy paths (password login, webmail-mediated OAuth handoff, QR pa
|
||||
- What RN does: `rewriteSessionUrls` only rewrites absolute URLs; `extractOrigin` returns null for `/jmap/` so the relative value is passed through unchanged and `secureFetch('/jmap/')` fails (`src/api/jmap-client.ts:320-340`). Same in the unified inbox `rewriteApiUrl` (`src/api/unified-inbox.ts:55-60`).
|
||||
- Fix hint: when the URL has no `^https?://`, return `serverOrigin + (url.startsWith('/') ? url : '/' + url)`; keep the plain string splitting (the comment about the RN URL polyfill corrupting templates still applies).
|
||||
|
||||
- [x] **Redirected session fetch may lose the Authorization header** — `P3` — `partial` — fixed in 0b1c240
|
||||
- [x] **Redirected session fetch may lose the Authorization header** — `P3` — `partial` — fixed in 0b1c240; the check keyed on `response.redirected`, which React Native's fetch never sets, so it only works since 859770b (audit B15)
|
||||
- What WEB does: `fetchSessionResponse` detects a redirected 200 with no accounts/username (Safari and some auth proxies drop `Authorization` on redirect) and refetches `response.url` with the header (`lib/jmap/client.ts:911-925`).
|
||||
- What RN does: `fetchSession` trusts the redirected response (`src/api/jmap-client.ts:363-383`); Stalwart 0.16.19 answers `/.well-known/jmap` with a 307 to `/jmap/session` (verified against stw-test19), and iOS `NSURLSession` can strip the header on redirect, which ends in `resolveAccountId` throwing "No account found in JMAP session".
|
||||
- Fix hint: mirror the WEB check (`response.redirected` and empty `accounts`/`username` → refetch `response.url` with the header).
|
||||
@@ -106,12 +106,12 @@ RN covers the happy paths (password login, webmail-mediated OAuth handoff, QR pa
|
||||
- What RN does: `connectWithOAuth` uses `session.username || accessToken.slice(0, 8)` (`src/api/jmap-client.ts:135-136`), so the same mailbox logged in via password and via OAuth can produce two registry entries, and the drawer synthesizes `username@host`.
|
||||
- Fix hint: read `Identity/get` after connect and use the primary identity email for `email` (and ideally for the registry id).
|
||||
|
||||
- [ ] **Cannot remove a non-active account** — `P2` — `missing` (changelog 1.7.8 "Remove a specific account from the switcher")
|
||||
- [x] **Cannot remove a non-active account** — `P2` — `missing` (changelog 1.7.8 "Remove a specific account from the switcher") — done in 6ae21d5, 490355c
|
||||
- What WEB does: per-row remove button in the switcher (`components/layout/account-switcher.tsx:138-143, 322-333`) backed by `removeAccount` which tears down the client, evicts caches and clears cookies (`stores/auth-store.ts:1382-1401`).
|
||||
- What RN does: only "Sign out of <active>" and "Sign out all" (`src/components/SidebarDrawer.tsx:400-437`).
|
||||
- Fix hint: add a per-row remove (long-press or trailing X) that runs `teardownPushNotificationsForAccount(id)`, `jmapClient.clearAccountCredentials(id)`, `useEmailStore.removeAccount(id)`, `accountStore.removeAccount(id)`.
|
||||
|
||||
- [ ] **Switcher shows stale connection state and no error indicator** — `P3` — `partial`
|
||||
- [x] **Switcher shows stale connection state and no error indicator** — `P3` — `partial` — done in 6ae21d5 (the drawer shows `hasError` with its message)
|
||||
- What WEB does: shows `hasError` (alert icon) with `errorMessage` and a live `isConnected` dot per account (`account-switcher.tsx:296-303`).
|
||||
- What RN does: renders only the `isConnected` dot (`SidebarDrawer.tsx:355-361`); `hasError`/`errorMessage` (set in `restoreSession`) are never displayed, and since only the active account is ever connected, other rows show whatever value they had last.
|
||||
- Fix hint: render `hasError`; show the dot only for the active account (or treat inactive as "cached").
|
||||
@@ -126,17 +126,17 @@ RN covers the happy paths (password login, webmail-mediated OAuth handoff, QR pa
|
||||
- What RN does: `authMode = props.authMode ?? 'basic'` and `quotaUsed = props.quotaUsed ?? 0` (`AccountSettings.tsx:46-47`) but `SettingsScreen` renders `<Component />` with no props (`SettingsScreen.tsx:242`), so OAuth accounts read "Basic" and the storage row never appears (RN has no `Quota/get` at all — flag for the mail/settings audit).
|
||||
- Fix hint: derive from `jmapClient.usesBearerAuth` (or persist `authMode` on `AccountEntry`); fetch quota via `Quota/get` when `urn:ietf:params:jmap:quota` is advertised.
|
||||
|
||||
- [ ] **Hard account cap of 5** — `P3` — `partial`
|
||||
- [x] **Hard account cap of 5** — `P3` — `partial` — done in 6ae21d5 (cap raised to 10, `src/lib/account-utils.ts:4`)
|
||||
- What WEB does: 5 on HTTP/1.1, lifted to 50 when HTTP/2 is observed, because each web account pins an SSE socket (`lib/account-utils.ts:75-107`).
|
||||
- What RN does: `MAX_ACCOUNTS = 5` (`src/lib/account-utils.ts:1`) although only the active account holds a live connection.
|
||||
- Fix hint: raise the constant (or drop the cap) once the unified inbox cost per account is acceptable.
|
||||
|
||||
- [ ] **Shared/group account settings scope is missing** — `P3` — `missing` (changelog 1.7.5 "Manage shared/group account settings from the Accounts page") — deferred: shared accounts are now listed read-only in AccountSettings (490355c); scoped filter/vacation stores need an accountId parameter first
|
||||
- [ ] **Shared/group account settings scope is missing** — `P3` — `missing` (changelog 1.7.5 "Manage shared/group account settings from the Accounts page") — partly: shared accounts are listed in AccountSettings (490355c), and tapping one scopes Filters and Vacation to it since a86cd1d; calendar and contacts settings are not scoped yet
|
||||
- What WEB does: lists `client.getSharedAccounts()` (non-primary) on the Accounts page and enters a scoped settings mode (filters, vacation, calendars, contacts) via `managed-account-store` (`components/settings/account-settings.tsx:43-48, 112-118, 225-262`; `stores/managed-account-store.ts`).
|
||||
- What RN does: `getSharedMailAccounts()` exists for mail/unified inbox (`src/api/jmap-client.ts:480-495`) but there is no shared-account listing or scoped settings.
|
||||
- Fix hint: list non-personal `session.accounts` in `AccountSettings` and pass an `accountId` into the filter/vacation stores.
|
||||
|
||||
- [ ] **Unified-inbox shared-account filter disagrees with `getSharedMailAccounts`** — `P3` — `rn-only-bug`
|
||||
- [x] **Unified-inbox shared-account filter disagrees with `getSharedMailAccounts`** — `P3` — `rn-only-bug` — done in 6ae21d5
|
||||
- What WEB does: a non-personal account counts as mail-capable even when its `accountCapabilities` omit mail (`lib/jmap/client.ts:4421-4445`); RN's own `getSharedMailAccounts` mirrors that (`jmap-client.ts:486-493`).
|
||||
- What RN does: the unified inbox requires `accountCapabilities == null || MAIL in accountCapabilities` (`src/api/unified-inbox.ts:208-210`), so a shared account that advertises other capabilities but not mail is silently skipped there while it appears in the sidebar.
|
||||
- Fix hint: reuse the `!advertisesMail && info.isPersonal → skip` rule from `getSharedMailAccounts`.
|
||||
|
||||
@@ -74,7 +74,7 @@ RN covers the core single-folder loop well (folder tree incl. shared/group accou
|
||||
|
||||
### Unified mailbox / cross-account views
|
||||
|
||||
- [x] **Unified scope: account-bounded by default, cross-account opt-in; single-account + group inboxes case** — `P2` — `partial` — fixed in 6ae21d5
|
||||
- [x] **Unified scope: account-bounded by default, cross-account opt-in; single-account + group inboxes case** — `P2` — `partial` — fixed in 6ae21d5; the drawer only shows the Unified section when it can hold more than one account's mail since af6290f (#843)
|
||||
- What WEB does: unified views stay inside the active account (own + shared/group folders) unless `unifiedCrossAccount` is on *and* the admin gate allows it (`components/mail/mail-app.tsx:465-500`, `stores/settings-store.ts:405-431`); the section is shown with one account as soon as `includeGroupInUnified && hasGroupInboxes` (`components/layout/sidebar.tsx:905-907`).
|
||||
- What RN does: "All inboxes" is shown only when `accounts.length > 1` (`src/components/SidebarDrawer.tsx:438-446`) and always spans every account (`src/api/unified-inbox.ts:236-257`). A single account with group inboxes gets no unified view even with "Include Group Inboxes" on (`ReadingSettings.tsx:257`). Admin policy gates do not exist in RN (see N/A).
|
||||
- Fix hint: show the row when `accounts.length > 1 || (includeGroupInUnified && mailboxes.some(m => m.isShared))`; add a `unifiedCrossAccount` toggle (default off, like WEB) and pass only the active account id when it is off. Note WEB's default for `includeGroupInUnified` is `true` (`settings-store.ts:639`) while RN defaults to `false` (`src/stores/settings-store.ts:235`).
|
||||
@@ -102,14 +102,14 @@ RN covers the core single-folder loop well (folder tree incl. shared/group accou
|
||||
- [x] **No actions on unified rows (swipe, star, read, delete, archive, spam, selection)** — `P2` — `missing` — fixed in 6ae21d5
|
||||
- What WEB does: every list action resolves the email's own client + owner account via `resolveEmailActionContext` (`stores/email-store.ts:581-640`), incl. batch actions grouped by `sourceAccountId` (`:2634-2660`, `:2740-2760`), archive into the owner's archive (`components/mail/mail-app.tsx:2141-2160`); changelog 1.7.5/1.7.7 "route counter/keyword updates to the email's own account in aggregate views".
|
||||
- What RN does: rows are plain `Pressable`s (`src/screens/UnifiedInboxScreen.tsx:95-138`); nothing but open.
|
||||
- Fix hint: wrap rows in `SwipeableRow`; because the JMAP client is single-account, actions on a *different registry account* need the detached `jmapPost` path (`unified-inbox.ts:116-131`) with `Email/set` against `email.jmapAccountId`; for the active account and its group accounts route through `setEmailKeywords(..., email.jmapAccountId)` / `moveEmail(...)` overrides.
|
||||
- Fix hint: wrap rows in `SwipeableRow`; because the JMAP client is single-account, actions on a *different registry account* need the detached `jmapPost` path (`unified-inbox.ts:116-131`) with `Email/set` against `email.jmapAccountId`; for the active account and its group accounts route through `setEmailKeywords(..., email.jmapAccountId)` / `moveEmail(...)` overrides. (`setEmailKeywords` is gone since 5041897; keyword changes now go through `patchKeywordsForEmails` in `src/api/email.ts` or the store's `setKeywordForEmails`.)
|
||||
|
||||
- [x] **Unified list stays stale after acting on a message; just-read mail handling** — `P3` — `partial` — fixed in 6ae21d5
|
||||
- What WEB does: aggregate views refresh through the fan-out on push (`refreshCurrentMailbox`, #791) and keep just-read/unstarred rows in the Unread/Starred views until re-opened (`retainedInViewIds`, `stores/email-store.ts:1007-1050`).
|
||||
- What RN does: `UnifiedInboxScreen` loads once per mount/`includeGroup` change (`:57-59`); after opening (mark read) or deleting in the thread screen and going back, the unread dot/row is stale until pull-to-refresh. No push hookup.
|
||||
- Fix hint: `useFocusEffect` → reload, or patch the local list from the thread screen result; when Unread/Starred views are added, keep the WEB retain semantics.
|
||||
|
||||
- [x] **Opening a unified-inbox message that is not in the active folder page shows another message's body** — `P1` — `rn-only-bug` — fixed in c8be383
|
||||
- [x] **Opening a unified-inbox message that is not in the active folder page shows another message's body** — `P1` — `rn-only-bug` — fixed in c8be383; the viewer's actions still hit the open folder's account and followed the live list until 5c7301b and 9c31de2 (audit B3, B5)
|
||||
- What WEB does: opens the clicked row object itself and fetches by source client/account (#847, `components/mail/mail-app.tsx:3111-3190`).
|
||||
- What RN does: `UnifiedInboxScreen.onOpen` switches account then navigates to `EmailThread` (`src/screens/UnifiedInboxScreen.tsx:61-86`). `EmailThreadScreen` pages over the *active folder's* `emails` (`src/screens/EmailThreadScreen.tsx:131-133`, FlatList `data={emails}` `:538`, `initialScrollIndex={Math.max(0, findIndex)}`). A group-inbox message (or any message not in the first page of the user's own inbox snapshot) is not in `emails`, so index 0 is shown: the pane renders `emails[0]` while the toolbar/`activeEmailId` (and delete/archive/spam) refer to the message that was tapped; with an empty snapshot the body area is blank. Bare-id `findIndex` also collides across accounts (Stalwart reuses id ranges, #847).
|
||||
- Fix hint: when `route.params.emailId` is not found in `emails` (or `jmapAccountId` is set), page over a one-element list `[{ id, threadId }]` instead of `emails`; compare ids together with the owning account.
|
||||
@@ -130,7 +130,7 @@ RN covers the core single-folder loop well (folder tree incl. shared/group accou
|
||||
- What RN does: `EmailRow` shows no tags; `tagPill/tagDot/tagText` styles exist but are unused (`src/screens/EmailListScreen.tsx:1481-1495`). Tags are only visible inside the thread screen tag menu.
|
||||
- Fix hint: compute tag ids from `item.keywords` (`$label:*`, `$color:*`), look up `useKeywordsStore().keywords` for label/colour (unknown ids → grey with raw id), render pills on the subject row; add a `tintListRowsByTag` setting if desired.
|
||||
|
||||
- [x] **Tag view (tap a tag → cross-folder `hasKeyword` list, #175) and tag counts in the drawer** — `P2` — `missing` — fixed in 6ae21d5
|
||||
- [x] **Tag view (tap a tag → cross-folder `hasKeyword` list, #175) and tag counts in the drawer** — `P2` — `missing` — fixed in 6ae21d5; the view and the badges cover shared accounts since 8d5bced (#1038), and the counts are no longer re-fetched on every drawer open since d097ae9
|
||||
- What WEB does: "Tags" sidebar section with unread/total per tag (`fetchTagCounts`, `stores/email-store.ts:1192`), visibility rules (`components/layout/sidebar.tsx:1002-1013`), `selectKeyword` → `getEmails(undefined, …, '$label:<id>')` across all folders (`stores/email-store.ts:1343-1420`, changelog 1.4.13 #175), counts kept in step with read/unread (1.7.8).
|
||||
- What RN does: no tag section in `SidebarDrawer`; `EmailFilters` has no keyword field (`src/stores/email-store.ts:140-149`).
|
||||
- Fix hint: add `keyword?: string` to `EmailFilters` and a "Tags" section in the drawer; when set, `buildJmapFilter` should omit `inMailbox` (search across folders) and add `{ hasKeyword: '$label:<id>' }`; counts via one `Email/query` (calculateTotal) per tag, optional.
|
||||
@@ -160,12 +160,12 @@ RN covers the core single-folder loop well (folder tree incl. shared/group accou
|
||||
- What RN does: tapping a draft opens the read-only `EmailThreadScreen`; `Compose` params have no draft/edit mode (`src/navigation/types.ts:7-24`), locale keys `email_viewer.draft_banner`/`edit_draft` exist but are unused.
|
||||
- Fix hint: in `handleRowPress`, if `item.keywords.$draft` (or folder role is drafts) navigate to `Compose` with a `draft: { id, to, cc, bcc, subject, body, blobId }` param; on send, destroy the old draft (composer area).
|
||||
|
||||
- [ ] **Permanent delete without confirmation (Trash, `deleteAction: permanent`, junk auto-permanent)** — `P2` — `bugfix-parity` — list side fixed in 3194b5e (single, batch, swipe via `src/lib/delete-confirm.ts`); deferred: thread screen `onDelete` belongs to the viewer area — use `confirmPermanentDelete()`/`isPermanentDelete()` from `src/lib/delete-confirm.ts`
|
||||
- [x] **Permanent delete without confirmation (Trash, `deleteAction: permanent`, junk auto-permanent)** — `P2` — `bugfix-parity` — list side fixed in 3194b5e (single, batch, swipe via `src/lib/delete-confirm.ts`); viewer side done in d2ed27f (`src/screens/EmailThreadScreen.tsx`)
|
||||
- What WEB does: confirm dialog before any permanent destroy, single and batch (`components/mail/mail-app.tsx:2086-2100`, `components/email/email-list.tsx:229-247`).
|
||||
- What RN does: `deleteEmail`/`deleteEmailsBatch` destroy immediately when in Trash, when `deleteAction === 'permanent'`, or junk + `permanentlyDeleteJunk` (`src/stores/email-store.ts:1286-1298`, `1449-1458`); the swipe fires without any prompt (`EmailListScreen.tsx:369-378`), and no undo is offered (correctly, `:1322-1324`).
|
||||
- Fix hint: compute `destroy` in the screen before calling the store (same rule) and show `Alert.alert` with Cancel/Delete; same for the thread screen `onDelete`.
|
||||
|
||||
- [ ] **"Never" mark-as-read (`markAsReadDelay === -1`) marks read instantly** — `P2` — `rn-only-bug`
|
||||
- [x] **"Never" mark-as-read (`markAsReadDelay === -1`) marks read instantly** — `P2` — `rn-only-bug` — fixed in d2ed27f
|
||||
- What WEB does: `-1` → never, `0` → instant, else timer (`components/mail/mail-app.tsx:1511-1528`).
|
||||
- What RN does: `if (markAsReadDelay > 0) { timer } else { markRead() }` (`src/screens/EmailThreadScreen.tsx:236-245`) so the "Never" option in `ReadingSettings.tsx:171` behaves like "Instant".
|
||||
- Fix hint: add `if (markAsReadDelay === -1) return;` before the branch.
|
||||
@@ -184,7 +184,7 @@ RN covers the core single-folder loop well (folder tree incl. shared/group accou
|
||||
- What RN does: `handleSwipeAction 'spam'` only checks `currentMailboxId !== junkMailboxId` (`src/screens/EmailListScreen.tsx:379-388`), so a swipe in Sent moves your own mail to Junk; in Junk the swipe silently does nothing.
|
||||
- Fix hint: skip when `currentMailbox.role` is sent/drafts; in junk call an `undoSpam` (see next item) and change the band label to "Not spam".
|
||||
|
||||
- [x] **Spam / not-spam do not flip `$junk`/`$notjunk` (#850) nor honour "trash-and-read"** — `P2` — `bugfix-parity` — fixed in 3194b5e
|
||||
- [x] **Spam / not-spam do not flip `$junk`/`$notjunk` (#850) nor honour "trash-and-read"** — `P2` — `bugfix-parity` — fixed in 3194b5e; the viewer's Spam and Not spam stayed a plain move until 5e42c67 (#695)
|
||||
- What WEB does: `markAsSpam` patches `mailboxIds` + `keywords/$junk: true` + `keywords/$notjunk: null` (+ `$seen` when `deleteAction === 'trash-and-read'`); `undoSpam` restores the original mailbox with `$junk: null`, `$notjunk: true` (`lib/jmap/client.ts:2422-2475`, store `stores/email-store.ts:2947-3100`, undo toast in `components/mail/mail-app.tsx:2228-2270`).
|
||||
- What RN does: swipe and thread-screen spam are plain `moveToMailbox` calls (`src/screens/EmailListScreen.tsx:381`, `src/screens/EmailThreadScreen.tsx:344-356`); the keywords stay untouched, so other clients/Stalwart's classifier never learn; undo label reads "Email moved to Junk".
|
||||
- Fix hint: add `markAsSpam(ids, junkRawId, accountId, alsoMarkRead)` / `undoSpam(ids, targetRawId, accountId)` to `src/api/email.ts` writing the keyword pointers; store actions with `pendingUndo.kind = 'spam'` (type already exists, `src/stores/email-store.ts:155`) whose `undoLast` also restores the keywords; queue-safe via an outbox `keywords` op.
|
||||
@@ -214,12 +214,12 @@ RN covers the core single-folder loop well (folder tree incl. shared/group accou
|
||||
- What RN does: 300 ms debounce into `setSearchQuery` → full `Email/query` + `Email/get` per pause (`src/screens/EmailListScreen.tsx:512-516`); with the `*` wildcard a single letter matches the whole mailbox.
|
||||
- Fix hint: search on `onSubmitEditing` (keep the clear button live), or debounce ≥ 600 ms with a minimum of 2 characters.
|
||||
|
||||
- [x] **Date format: no regional date-locale setting; relative strings hard-coded** — `P3` — `partial` — fixed in 0205aa0
|
||||
- [ ] **Date format: no regional date-locale setting; relative strings hard-coded** — `P3` — `partial` — partly: 0205aa0 localized the relative strings; there is still no `dateLocale` setting (see area 08, "Language list not localized")
|
||||
- What WEB does: user-selectable regional format (`dateLocale`, changelog 1.7.7) and a preset picker (#331); strings localized.
|
||||
- What RN does: `formatListDate` follows the app locale only (`src/lib/date-format.ts:17-31`), "Just now"/"m ago" are English (`:38-41`).
|
||||
- Fix hint: localize the relative strings via `t()`; add a `dateLocale` select if parity is wanted.
|
||||
|
||||
- [x] **Show avatars in Junk (off by default, 1.5.1); avatar priority** — `P3` — `partial` — fixed in bea3fc4
|
||||
- [x] **Show avatars in Junk (off by default, 1.5.1); avatar priority** — `P3` — `partial` — fixed in bea3fc4; contact photos win over favicons and initials since eca3871 (#54)
|
||||
- What WEB does: `showAvatarsInJunk` hides favicon/photo images in Junk (`components/email/thread-list-item.tsx:137`); avatar priority contact photo > plugin (Gravatar) > favicon > initials (`components/ui/avatar.tsx:236`).
|
||||
- What RN does: favicons always load, including in Junk (`src/components/SenderAvatar.tsx:37`); no contact-photo lookup.
|
||||
- Fix hint: pass `disableImages={currentMailbox.role === 'junk' && !showAvatarsInJunk}` into `SenderAvatar`; contact photos optional.
|
||||
@@ -241,7 +241,7 @@ RN covers the core single-folder loop well (folder tree incl. shared/group accou
|
||||
|
||||
### Search
|
||||
|
||||
- [x] **Search folder scope: always the current folder; no all-folders default (#788) and no folder picker** — `P2` — `bugfix-parity` — fixed in 3194b5e
|
||||
- [x] **Search folder scope: always the current folder; no all-folders default (#788) and no folder picker** — `P2` — `bugfix-parity` — fixed in 3194b5e; "All folders" only searched the open folder's account until ee50418 (#1082)
|
||||
- What WEB does: `searchMailboxId` defaults to `""` = all folders, is a separate, persisted choice in the filter panel (`stores/email-store.ts:95-101`, `2344-2378`, changelog 1.9.0 #788); cross-mailbox queries.
|
||||
- What RN does: `buildJmapFilter` always starts with `{ inMailbox: current }` (`src/stores/email-store.ts:255`); the filter modal has no folder field.
|
||||
- Fix hint: add `folder?: string | 'all'` to `EmailFilters` (default `'all'` while a text query or filter is active, i.e. omit `inMailbox`); expose a folder select in the modal; the base-view snapshot logic already keys off `isBaseView`.
|
||||
@@ -291,11 +291,11 @@ RN covers the core single-folder loop well (folder tree incl. shared/group accou
|
||||
- Missing Archive/Trash/Junk → error alert (`EmailListScreen.tsx:363-387`), never silently destroys when Trash is missing (#195 equivalent).
|
||||
- Delete semantics: trash / trash-and-read (#323) / permanent / junk auto-permanent, single and batch (`email-store.ts:1268-1335`, `1434-1527`); undo snackbar for archive/delete/move (single + batch) — RN is ahead of WEB here.
|
||||
- Archive single / by year / by month with folder creation in one request, batch archive, "Reorganize existing archive" (`src/api/email.ts:579-692`, `ReadingSettings.tsx:105-159`).
|
||||
- Swipe actions: configurable per direction, instant and reveal modes, pure gesture module with tests, stale-props fix (`SwipeableRow.tsx`, `swipe-gesture.ts`); more actions than WEB (pin, move). RTL: RN ships no RTL locales, nothing to do yet.
|
||||
- Swipe actions: configurable per direction, instant and reveal modes, pure gesture module with tests, stale-props fix (`SwipeableRow.tsx`, `swipe-gesture.ts`); more actions than WEB (pin, move). RTL: ar, he and fa ship since 990cd84, but swipe directions are not mirrored yet (area 08, RTL finding).
|
||||
- Multi-select: long-press, select-all-visible/indeterminate, batch star/read/tag/move/archive/delete (`EmailListScreen.tsx:320-500`).
|
||||
- Pagination via `onEndReached`, pull-to-refresh, incremental `Email/queryChanges` + `Email/changes` refresh with per-account state tokens, base-view restore after search (#10), sort change invalidates snapshots (#5), offline seed and fallback.
|
||||
- Search: full-text with wildcard suffix (`src/lib/search-utils.ts` = WEB `toWildcardQuery`), from/to/subject/date/attachment/unread/starred tri-state filters, chips row, search inside a shared folder routed to its owner.
|
||||
- Keyword writes send the whole `keywords` map (no JSON-pointer escaping issue, no `false` values) — not affected by 5c484af1 / be97c8bf; tag ids normalized exactly like WEB `normalizeKeywordLevel` (`KeywordSettings.tsx:118-123`); batch tag toggle via `TagSheet` with "all have it" semantics.
|
||||
- Keyword writes send RFC 6901-escaped `keywords/<name>` patch pointers since 5041897 (`src/api/patch-pointer.ts`; the whole-map writes erased other keywords, audit B4), with `null` instead of `false` — not affected by 5c484af1 / be97c8bf; tag ids normalized exactly like WEB `normalizeKeywordLevel` (`KeywordSettings.tsx:118-123`); batch tag toggle via `TagSheet` with "all have it" semantics.
|
||||
- Unread dot (#27), date formats smart/relative/full with 12/24h, preview toggle, density-aware rows, sender favicon avatars with failure cache, `.eml`/`.zip` import into the open folder, Scheduled quick view, "Include group inboxes" setting and shared badge/account dot in the unified list.
|
||||
- Mark-as-read delay (0/3s/5s) in the thread screen; both clients still display `receivedAt` in the list (WEB `lib/email-date.ts` is not wired into the list yet).
|
||||
|
||||
@@ -305,6 +305,6 @@ RN covers the core single-folder loop well (folder tree incl. shared/group accou
|
||||
- Admin policy gates for unified/cross views (`usePolicyStore` feature flags) — RN has no admin policy channel; only the user-side toggles apply.
|
||||
- Return-to-list-after-action setting — the RN thread screen always returns after delete/archive/spam.
|
||||
- "Refresh" toolbar button and F5/Ctrl+R interception — pull-to-refresh covers it.
|
||||
- Favicon unread badge, sidebar collapse/resize, Pro multi-pane shell, folder subscription (neither client exposes `isSubscribed`).
|
||||
- Favicon unread badge (its native counterpart, the app-icon badge, landed in 25c0883, iOS only), sidebar collapse/resize, Pro multi-pane shell, a folder subscription UI (neither client shows `isSubscribed`; both subscribe the folders they create, native since 30aea63, #951).
|
||||
- Plugin search hooks / external search results, `onEmailsFetched` transforms.
|
||||
- Source-folder chip on rows is N/A *until* All-mail/cross views exist in RN.
|
||||
|
||||
@@ -12,7 +12,7 @@ RN has a solid single-message reader (WebView body with CSP, shrink-to-fit + pin
|
||||
- What RN does: the list collapses same-thread rows to the newest message with a count badge (`src/screens/EmailListScreen.tsx:224-238`, default `disableThreading: false` at `src/stores/settings-store.ts:266`), the row opens `EmailThreadScreen` with a single `emailId` (`App.tsx:145-151`), and the screen only ever shows that one message (`src/screens/EmailThreadScreen.tsx:113-120`, `685-840`). `getThread` exists in `src/api/email.ts:448` but is only used by a test. Older messages of a thread cannot be opened at all unless the user turns on "Disable Thread Grouping".
|
||||
- Fix hint: on open, call `getThread(threadId, ownerAccountId)` + `getFullEmails(ids)` and render a vertical list of collapsible message cards inside the pane (newest + unread expanded), reusing `EmailBodyView` per card; keep the horizontal pager for thread-to-thread navigation.
|
||||
|
||||
- [x] **Pager shows the wrong message (or nothing) when the opened email is not in the store list — fixed in c8be383** — `P1` — `rn-only-bug`
|
||||
- [x] **Pager shows the wrong message (or nothing) when the opened email is not in the store list — fixed in c8be383** — `P1` — `rn-only-bug` — the body was right after c8be383, but delete, archive, move and spam hit the open folder's account, and new mail moved the page, until 5c7301b and 9c31de2 (audit B3, B5)
|
||||
- What WEB does: the viewer renders `selectedEmail` directly; prev/next are derived from the current list (`components/email/email-viewer.tsx:5220-5279`).
|
||||
- What RN does: the pager is a `FlatList` over `useEmailStore(s => s.emails)` and the initial index is `Math.max(0, emails.findIndex(id))` (`src/screens/EmailThreadScreen.tsx:128-133`, `535-572`). `UnifiedInboxScreen` keeps its own local list and never writes the store (`src/screens/UnifiedInboxScreen.tsx:35-50`, `61-84`), `ContactActivity` navigates with ids from a contact search (`src/components/contacts/ContactActivity.tsx:170-177`). In both cases the opened id is absent from `emails`, so the visible pane is `emails[0]` (a different mailbox's first message, or blank when the list is empty) while the toolbar/mark-read/delete act on `activeEmailId`.
|
||||
- Fix hint: when `route.params.emailId` is not in `emails`, render a single-item pager (`data=[{id: emailId}]`) and disable prev/next; or have the unified inbox / contact activity pass their own id list through route params.
|
||||
@@ -104,7 +104,7 @@ RN has a solid single-message reader (WebView body with CSP, shrink-to-fit + pin
|
||||
- What RN does: none.
|
||||
- Fix hint: port `parseUnsubscribeUrls`/`isValidUnsubscribeUrl` from `lib/validation.ts`; for mailto call `sendEmail` with the parsed fields; open http via `expo-web-browser`.
|
||||
|
||||
- [x] **Read receipts (MDN, RFC 8098): request banner, send, "always/ask/never", `$mdnsent` — fixed in d2ed27f** — `P2` — `missing`
|
||||
- [x] **Read receipts (MDN, RFC 8098): request banner, send, "always/ask/never", `$mdnsent` — fixed in d2ed27f** — `P2` — `missing` — the receipt headers were not sanitized (GHSA-w38p) and "Always send" answered the unseen neighbour pages until cb6ddd6 and 5f3f584 (audit S1, B7)
|
||||
- What WEB does: detects `Disposition-Notification-To`, offers Send/Ignore (or auto-sends in "always" mode) only in received folders, builds the multipart/report with `lib/mdn.ts`, uploads → `Email/import` into Sent → `EmailSubmission/set` with explicit envelope, then sets `$mdnsent` (`components/email/email-viewer.tsx:2686-2784`, `lib/jmap/client.ts:4080-4130`, `components/email/read-receipt-banner.tsx`).
|
||||
- What RN does: none; no `readReceiptResponse` setting.
|
||||
- Fix hint: port `lib/mdn.ts` verbatim (pure string builder), reuse `uploadBytes` (`src/api/blob.ts:67`) + `importEmailBlob` (`src/api/email.ts:423`) + an `EmailSubmission/set` helper; add the setting.
|
||||
@@ -124,7 +124,7 @@ RN has a solid single-message reader (WebView body with CSP, shrink-to-fit + pin
|
||||
- [x] **Tags, `$important` badge and answered/forwarded state in the header — fixed in d2ed27f** — `P3` — `missing`
|
||||
- What WEB does: tag badges (removable) under the subject, "Important" pill (`components/email/email-viewer.tsx:3789-3806`); `$answered`/`$forwarded` set after send (`components/mail/mail-app.tsx:1663-1667`).
|
||||
- What RN does: Tag sheet exists but no badges are rendered in the pane; `$answered`/`$forwarded` are never set after a reply/forward (no hit for `$answered` in `src/`).
|
||||
- Fix hint: render `keywordDefs` matches from `email.keywords` under the subject; after a successful send in `ComposeScreen` call `setEmailKeywords(originalId, {...keywords, $answered|$forwarded: true}, accountId)`.
|
||||
- Fix hint: render `keywordDefs` matches from `email.keywords` under the subject; after a successful send in `ComposeScreen` call `setEmailKeywords(originalId, {...keywords, $answered|$forwarded: true}, accountId)`. (`setEmailKeywords` is gone since 5041897: set `keywords/$answered` or `keywords/$forwarded` through `patchKeywordsForEmails` or the store's `setKeywordForEmails`.)
|
||||
|
||||
### Calendar invitation banner
|
||||
|
||||
@@ -138,7 +138,7 @@ RN has a solid single-message reader (WebView body with CSP, shrink-to-fit + pin
|
||||
- What RN does: `extractMethodFromRawIcs` exists (`src/lib/calendar-invitation.ts:103-106`) but is never called; only `inferInvitationMethod` heuristics are used (`CalendarInvitationBanner.tsx:62`), so a cancellation whose event status is not `cancelled`, or a REPLY from a single attendee, is shown as a plain invitation with RSVP buttons.
|
||||
- Fix hint: fetch the blob text (`getDownloadUrl` + `secureFetch`, as `fetchRawEmail` does) in parallel with parse and prefer `extractMethodFromRawIcs`.
|
||||
|
||||
- [ ] **Banner lacks WEB's trust assessment, actor summary, existing-event / "already in calendar" state, calendar picker, "View in calendar", collapse, sequence badge, counter-proposal review** — `P3` — `partial` — deferred: trust assessment, iTIP method and calendar picker landed in 8206893 (calendar agent); existing-event state, "View in calendar", collapse, sequence badge and counter-proposal review remain open in the calendar area
|
||||
- [ ] **Banner lacks WEB's trust assessment, actor summary, existing-event / "already in calendar" state, calendar picker, "View in calendar", collapse, sequence badge, counter-proposal review** — `P3` — `partial` — deferred: trust assessment, iTIP method and calendar picker landed in 8206893 (calendar agent); the existing-event state followed in c0582ab, which looks the event up on the server by `uid` (also so Accept sends the RSVP, audit B22); "View in calendar", collapse, sequence badge and counter-proposal review remain open in the calendar area
|
||||
- What WEB does: `getInvitationTrustAssessment` (sender vs organizer + auth results) warning, actor line ("X accepted"), `queryCalendarEvents({uid})` to show "already in calendar" and current RSVP, picker when >1 calendar, "View in calendar" navigation, collapsible card, `sequence` "updated" pill, apply counter proposal for organizers (`components/email/calendar-invitation-banner.tsx:392-474`, `502-553`, `657-672`, `721-752`, `802-1140`).
|
||||
- What RN does: title/date/location/video/organizer rows, Yes/Maybe/No, Add to first writable calendar (`src/components/email/CalendarInvitationBanner.tsx:83-207`); no dedupe check before offering "Add", no picker, no trust warning, no navigation to the event.
|
||||
- Fix hint: port `getInvitationTrustAssessment` (needs the headers finding for auth), look up `useCalendarStore.events` by `uid` to switch to "already in calendar"/current response, add a calendar picker sheet when `calendars.length > 1`, and a "View in calendar" button that navigates to `CalendarScreen` with the start date.
|
||||
@@ -165,7 +165,7 @@ RN has a solid single-message reader (WebView body with CSP, shrink-to-fit + pin
|
||||
- What RN does: shows `winmail.dat` as an opaque attachment; the real files inside are unreachable.
|
||||
- Fix hint: `lib/tnef.ts` is pure `Uint8Array` code; copy it and feed `jmapClient.fetchBlobArrayBuffer`.
|
||||
|
||||
- [x] **Attachment chip list: no MIME-based fallback names, MDN/DSN report parts and non-inline cid images mishandled — fixed in d2ed27f** — `P3` — `partial`
|
||||
- [x] **Attachment chip list: no MIME-based fallback names, MDN/DSN report parts and non-inline cid images mishandled — fixed in d2ed27f** — `P3` — `partial` — body-embedded `cid:` parts typed octet-stream are hidden from the chips since 1d1d6a5
|
||||
- What WEB does: unnamed parts get `Document.pdf`/`Email.eml`/`Attachment.<sub>` (`components/email/email-viewer.tsx:191-225`); `message/disposition-notification` and `message/delivery-status` parts are hidden (`1604-1606`); inline hiding requires `disposition === 'inline'` (`1601-1603`).
|
||||
- What RN does: unnamed → `'attachment'` (`src/screens/EmailThreadScreen.tsx:745`); report parts are listed; any `cid` image is hidden regardless of disposition (`719-721`), so an image attached with a Content-ID but `disposition: attachment` (common from some clients) disappears from the list even when the body does not reference it.
|
||||
- Fix hint: port `getAttachmentDisplayName`, add the two report-type filters, and check `disposition === 'inline'` (or that the body actually references the cid via `extractCidRefs`).
|
||||
@@ -247,7 +247,7 @@ RN has a solid single-message reader (WebView body with CSP, shrink-to-fit + pin
|
||||
- External content policy ask/block/allow, per-message "Load external content", "Trust sender" persisting locally and to the address book, trusted list from the address book and contacts (`src/components/EmailBodyView.tsx:480-535`, `643-672`).
|
||||
- cid inline images fetched with auth and injected as data URIs, transparent placeholder otherwise (`src/components/EmailBodyView.tsx:546-584`, `src/lib/email-html.ts:213-218`).
|
||||
- Shrink-to-fit of fixed-width emails + in-page pinch zoom/pan with pager/scroll locking (`src/components/EmailBodyView.tsx:159-460`) — RN-only, better than WEB's horizontal scroll (#409).
|
||||
- Mark read on open with delay setting (apart from the `-1` bug), optimistic star/unread/tag toggles, delete/archive/spam/not-spam/move scoped to the message's account, `no trash folder` alert (`src/screens/EmailThreadScreen.tsx:221-364`).
|
||||
- Mark read on open with delay setting (apart from the `-1` bug), optimistic star/unread/tag toggles, delete/archive/spam/not-spam/move scoped to the message's account, `no trash folder` alert (`src/screens/EmailThreadScreen.tsx:221-364`). The account scoping was wrong for messages opened from the Unified Inbox, a notification or a deep link until 5c7301b (audit B3); star, tag and unread are queued offline since ddff2f7.
|
||||
- Prev/next navigation with prefetched neighbour panes and swipe (`src/screens/EmailThreadScreen.tsx:122-181`, `535-572`) — RN-only pager.
|
||||
- Skeleton while loading (`src/screens/EmailThreadScreen.tsx:845-886`).
|
||||
- Attachments: chips with size, show-all toggle, `hideInlineImageAttachments` setting, `attachmentPosition` setting, `mailAttachmentAction` preview/download, templated filenames + transforms identical to WEB (`src/lib/download-filename.ts` diff vs `lib/download-filename.ts` is whitespace/quotes only, minus `DEFAULT_BUNDLE_TEMPLATE`/`BUNDLE_TOKENS`), Downloads settings screen with live preview (`src/components/settings/DownloadsSettings.tsx`).
|
||||
@@ -259,7 +259,7 @@ RN has a solid single-message reader (WebView body with CSP, shrink-to-fit + pin
|
||||
|
||||
## N/A on mobile
|
||||
- Print (WEB `handlePrint`, `components/email/email-viewer.tsx:2621-2670`) — could be done with `expo-print` later, but not a parity requirement.
|
||||
- Copy permalink / deep link to a message (#733) — RN has no linking configuration; no `linking` prop in `App.tsx`.
|
||||
- Copy permalink to a message (#733) — RN opens incoming message links since 5802cae (`src/navigation/linking.ts`) but has no "copy link" action.
|
||||
- Fullscreen reading toggle, Pro split panes, toolbar overflow measurement, plugin slots/hooks (`email-banner`, `email-detail-sidebar`, `onRenderEmailBody`, `onBeforeExternalLink`, `ui.rerenderEmail`), keyboard shortcuts, drag attachments out to the file system (#267), drag emails out as `.eml`, import `.eml` from the viewer menu (RN imports from the list screen instead).
|
||||
- Iframe-specific fixes: "Render the email body on DOM parse instead of iframe load" (#635), iframe flash/flicker fixes (1.5.4, 1.6.7), `blob:` in CSP `object-src`/`frame-src` for PDF previews (#253), pdf.js bytes vs `blob:` fetch (#871), RSC 307 loop (#919), iOS Safari viewport zoom on input focus (#838).
|
||||
- Demo-mode welcome pane, tour, contact sidebar panel layout (desktop only; the actions themselves are listed above).
|
||||
|
||||
@@ -9,7 +9,7 @@ Legend for refs: WEB paths are relative to `the webmail repo`, RN paths to `the
|
||||
|
||||
### Drafts
|
||||
|
||||
- [x] **No draft autosave / save-on-close — closing a compose loses everything** — fixed in 5b72c4d — `P1` — `missing`
|
||||
- [x] **No draft autosave / save-on-close — closing a compose loses everything** — fixed in 5b72c4d — `P1` — `missing` — offline or without a Drafts folder the close dialog still dropped the text, and the iOS swipe skipped the dialog, until 92b42e4 (audit B16)
|
||||
- What WEB does: debounced 2 s autosave to the server Drafts folder with `$draft`+`$seen` keywords (`components/email/email-composer.tsx:1648-1815` saveDraftOnce, `:1842-1883` timer; `lib/jmap/client.ts:2998-3060` createDraft), serialized against send (#303, `:1821-1837`), `beforeunload` best-effort save (`:1082-1091`), "Save / Discard / Cancel" close dialog (`:2347-2426`, `handleSaveDraftAndClose`, `handleDiscardAndClose`), toast when the save is refused so text is not dropped (`:2384-2401`, #702).
|
||||
- What RN does: the only close path is `onClose` in `src/screens/ComposeScreen.tsx:518-541`, an Alert with Cancel / **Discard** — no "Save draft" option and no server draft is ever created. The hardware back button / iOS swipe-back is not intercepted at all (no `beforeRemove` listener anywhere in `ComposeScreen.tsx` or `src/navigation/*`), so the dirty guard is bypassed entirely by the OS back gesture.
|
||||
- Fix hint: add a `createDraft(...)`-style call in `src/api/email.ts` (Email/set create into the `drafts`-role mailbox with `$draft`/`$seen`, destroy previous draft id only after the new create succeeds), call it from a debounced effect + from the close Alert ("Save draft" button), and register `navigation.addListener('beforeRemove', …)` to route back gestures through the same guard.
|
||||
@@ -54,7 +54,7 @@ Legend for refs: WEB paths are relative to `the webmail repo`, RN paths to `the
|
||||
- [x] **Reply / forward does not mark the original `$answered` / `$forwarded`** — fixed in 7f956d6 — `P2` — `missing`
|
||||
- What WEB does: after a successful reply/forward the original gets `$answered` or `$forwarded` (`components/mail/mail-app.tsx:1663-1673`), routed to the owning account.
|
||||
- What RN does: nothing — `grep -F '$answered'` over `src/` returns no hits; `performSend` (`ComposeScreen.tsx:941-974`) has no access to the original email id (`replyTo` params in `src/navigation/types.ts:10-19` carry no `emailId`).
|
||||
- Fix hint: add `originalEmailId`/`jmapAccountId` to the `replyTo` route param and call the existing keyword primitive (`setEmailKeywords` via `applyOrQueue` in `src/stores/outbox-store.ts`) after the send resolves.
|
||||
- Fix hint: add `originalEmailId`/`jmapAccountId` to the `replyTo` route param and call the existing keyword primitive (`setEmailKeywords` via `applyOrQueue` in `src/stores/outbox-store.ts`) after the send resolves. (`setEmailKeywords` is gone since 5041897; the keyword primitive is now `patchKeywordsForEmails`, and the outbox queues `keywords/<name>` patches.)
|
||||
|
||||
- [x] **Identity `replyTo` / `bcc` are never applied to outgoing mail** — fixed in 7f956d6 — `P2` — `missing`
|
||||
- What WEB does: `sendEmail` copies the identity's `replyTo` onto the message (`lib/jmap/client.ts:3167-3172`, `:3184`); identity form lets users edit Reply-To and Bcc (`components/identity/identity-form.tsx:39-41`, `:66-70`, `:132-133`).
|
||||
@@ -221,7 +221,7 @@ Legend for refs: WEB paths are relative to `the webmail repo`, RN paths to `the
|
||||
|
||||
### Scheduled send
|
||||
|
||||
- [x] **Scheduled screen: cancel only — no reschedule, no "send now", no edit** — fixed in c659b54 — `P2` — `partial`
|
||||
- [x] **Scheduled screen: cancel only — no reschedule, no "send now", no edit** — fixed in c659b54 — `P2` — `partial` — reschedule dropped Cc and Bcc until e67947a, and a refused half of it could drop or double-send the message until 7e598c5 (audit B19)
|
||||
- What WEB does: scheduled view with send-now button (changelog 1.6.x), reschedule (`stores/email-store.ts:4353` rescheduleScheduledEmail, `lib/jmap/client.ts:7985`), cancel-for-edit that reopens the composer (`email-store.ts:4326`), listing routed to the owning account (#874).
|
||||
- What RN does: `src/screens/ScheduledScreen.tsx:51-78` offers only "Cancel send"; `src/api/email.ts:902-988` has `listScheduledEmails`/`cancelScheduledSend` only.
|
||||
- Fix hint: add `rescheduleScheduledSend(submissionId, emailId, identityId, holdFor)` = `EmailSubmission/set { update: {id: {undoStatus:'canceled'}}, create: {replacement: {emailId, identityId, envelope(HOLDFOR)}} }` and an edit path once drafts exist (cancel → open composer prefilled from the Email).
|
||||
@@ -253,7 +253,7 @@ Legend for refs: WEB paths are relative to `the webmail repo`, RN paths to `the
|
||||
|
||||
### mailto / share intents
|
||||
|
||||
- [ ] **No `mailto:` handling / share-to-app intent** — `P2` — `missing` — composer side (prefill*, prefillAttachments upload) done in 5b72c4d; scheme/intent filters + Linking → Compose belong to area 08
|
||||
- [x] **No `mailto:` handling / share-to-app intent** — `P2` — `missing` — composer side (prefill*, prefillAttachments upload) done in 5b72c4d; scheme, intent filters and links to Compose done in 5802cae (area 08); shared files, `SENDTO` and `+` addresses work since cd4ce9d, 06e8942, ee652d3 (audit B9–B11)
|
||||
- What WEB does: PWA `protocol_handlers` for mailto (`app/manifest.ts:118`), `parseMailtoUrl` (`lib/validation.ts:133-167`), in-app composer open through the unsaved-draft guard (`components/mail/mail-app.tsx:1110`, changelog 1.9.0).
|
||||
- What RN does: `app.config.js:29` registers only the custom `bulwarkmobile` scheme; no `intentFilters` (Android `mailto`/`SEND`) or iOS `LSApplicationQueriesSchemes`/`CFBundleURLTypes` for `mailto`; no `Linking.getInitialURL`/`addEventListener('url')` handling that routes to `Compose` (grep over `App.tsx`/`src/navigation` shows none). Tapping a mailto link in another app cannot open Bulwark; sharing a file/text to the app is impossible.
|
||||
- Fix hint: add `scheme: ['bulwarkmobile', 'mailto']` + Android `intentFilters` for `mailto` and `android.intent.action.SEND`, parse with a port of `parseMailtoUrl`, and navigate to `Compose` with `prefillTo`/subject/body (add those params); for SEND intents seed `attachments` from the shared `content://` URI (upload path already supports it, `src/api/blob.ts:17-22`).
|
||||
@@ -281,7 +281,7 @@ Legend for refs: WEB paths are relative to `the webmail repo`, RN paths to `the
|
||||
- Fix hint: route through `t()` with keys in `locales/<lang>/common.json`.
|
||||
|
||||
## Verified at parity (brief list, so the fixer knows what NOT to redo)
|
||||
- Scheduled send via `EmailSubmission` envelope `HOLDFOR` with explicit `rcptTo` (bare addresses, names stripped) and `maxDelayedSend`/FUTURERELEASE capability checks: `src/api/email.ts:828-844`, `src/api/jmap-client.ts:513-531`, `ComposeScreen.tsx:796-821` — matches `lib/jmap/client.ts:592-612`, `:3245-3258`.
|
||||
- Scheduled send via `EmailSubmission` envelope `HOLDFOR` with explicit `rcptTo` (bare addresses, names stripped) and `maxDelayedSend`/FUTURERELEASE capability checks: `src/api/email.ts:828-844`, `src/api/jmap-client.ts:513-531`, `ComposeScreen.tsx:796-821` — matches `lib/jmap/client.ts:592-612`, `:3245-3258`. The capability check read only the session-level object, which Stalwart leaves empty, so scheduling and the undo delay were off on Stalwart until e28e8b4 (#57, audit B8); holds are capped at Stalwart's 7-day limit since 4e7ae2f.
|
||||
- Undo-send delay setting (0/5/10/20/30 s) applied only when the server supports delayed send: `ComposeScreen.tsx:825-828`, `ComposingSettings.tsx:42-48`.
|
||||
- Send-time readback of the live editor DOM with timeout + loud abort (RN #9/#11): `RichTextEditor.tsx:143-152`, `ComposeScreen.tsx:890-902`; escape-free page script with a parse test.
|
||||
- `Name <addr>` recipients split into JMAP `name`/`email` (#672): `ComposeScreen.tsx:69-84`, `:944-947`.
|
||||
@@ -310,4 +310,4 @@ Legend for refs: WEB paths are relative to `the webmail repo`, RN paths to `the
|
||||
- Signature "double-click to unlock" atom (Tiptap-specific); the RN contenteditable is always editable.
|
||||
- Quoted-HTML shadow-island typing detection (#654) — Tiptap/shadow-DOM specific.
|
||||
- Send through another logged-in account's client for DKIM (#461) — RN is single-active-account; identities always belong to the submitting account.
|
||||
- Route scheduled sends / identity on reschedule to a shared account (#874, a15af722) — RN does not compose on behalf of shared accounts.
|
||||
- Route scheduled sends / identity on reschedule to a shared account (#874, a15af722) — RN does not compose on behalf of shared accounts, but the Scheduled screen lists, cancels, reschedules and edits sends held in shared accounts since 72f48db.
|
||||
|
||||
+12
-16
@@ -10,7 +10,7 @@ RN has a solid read path (month/week/agenda, shared-calendar namespacing, per-vi
|
||||
- [x] **Recurrence scope dialog is a stub: "This event" / "This and following" behave as "All events"** — `P1` — `missing` — fixed in 161baf1
|
||||
- What WEB does: `handleScopeSelect` implements all three scopes: "this" writes a `recurrenceOverrides/<recurrenceId>` patch (or the synthetic id on Stalwart >= 0.16.20), "this_and_future" truncates the master with `until` and creates a new series, "all" patches the master; delete does the mirror (exclude / truncate / destroy) and refetches the range (ref `components/calendar/calendar-app.tsx:842-998`, `lib/recurrence-overrides.ts:21-32`, changelog 1.9.0 "Edit a single recurring occurrence via a one-shot override patch").
|
||||
- What RN does: `handleScopeSelect` ignores the chosen scope with a TODO ("treat all scopes as 'all'") and either opens the edit modal on the occurrence or deletes `action.event.id`, which the store resolves to the master via `originalId` (ref `src/screens/CalendarScreen.tsx:347-363`, `src/stores/calendar-store.ts:306-329`). Choosing "This event" on delete destroys the entire series.
|
||||
- Fix hint: port WEB's `handleScopeSelect`/`truncateRecurrenceAtEvent`/`findMasterEvent` into CalendarScreen; add `buildRecurrenceOverridePatch` (WEB `lib/recurrence-overrides.ts`) and `RECURRENCE_OVERRIDE_IMMUTABLE_KEYS`; for "this" write `{ [`recurrenceOverrides/${recurrenceId}`]: override }` on `originalId`; for delete-this write `{ excluded: true }`; refresh after.
|
||||
- Fix hint: port WEB's `handleScopeSelect`/`truncateRecurrenceAtEvent`/`findMasterEvent` into CalendarScreen; add `buildRecurrenceOverridePatch` (WEB `lib/recurrence-overrides.ts`; removed again in f397ee3, where "This event only" goes to `updateEvent`/`deleteEvent` with the occurrence's id) and `RECURRENCE_OVERRIDE_IMMUTABLE_KEYS`; for "this" write `{ [`recurrenceOverrides/${recurrenceId}`]: override }` on `originalId`; for delete-this write `{ excluded: true }`; refresh after.
|
||||
|
||||
- [x] **Editing an expanded occurrence rewrites the master's `start` to the occurrence date (series shifts / earlier occurrences vanish)** — `P1` — `bug` — fixed in 161baf1
|
||||
- What WEB does: for scope "all" the updates go to the master but `start` from an occurrence is never applied to the master unless the user changed it; scope "this" goes into an override (ref `components/calendar/calendar-app.tsx:930-943`).
|
||||
@@ -27,7 +27,7 @@ RN has a solid read path (month/week/agenda, shared-calendar namespacing, per-vi
|
||||
- What RN does: `buildRuleFromEditorValue` sets `rscale: 'gregorian', skip: 'omit'` on every custom rule (ref `src/lib/recurrence.ts:220-221`). RN affected: any custom recurrence created on mobile breaks Android CalDAV sync.
|
||||
- Fix hint: delete the two properties in `buildRuleFromEditorValue`; the simple presets in `EventModal.tsx:143-146` are already clean.
|
||||
|
||||
- [ ] **Synthetic occurrence ids / server-side expansion (Stalwart >= 0.16.20) not used** — `P3` — `missing` — deferred: optional; client-side expansion is at parity and the scope dialog now writes overrides through the master, so the synthetic-id path buys nothing functionally yet
|
||||
- [x] **Synthetic occurrence ids / server-side expansion (Stalwart >= 0.16.20) not used** — `P3` — `missing` — done in 8454889 (server expansion when the probe finds synthetic ids, device-side expansion otherwise), f397ee3 (one occurrence changed or deleted through its own id) and 96c5a3f (RSVP to one occurrence)
|
||||
- What WEB does: probes `CalendarEvent/set` with `h333333` once, then queries with `expandRecurrences: true`, hydrates occurrences from base events and writes occurrence patches through the synthetic id with a fallback to base-event overrides (ref `lib/jmap/client.ts:5648-5680, 5747-5751, 5828-5868`, `lib/recurrence-instances.ts:35, 73, 208-232`, `stores/calendar-store.ts:82-184`).
|
||||
- What RN does: always fetches raw events and expands on the client (`src/stores/calendar-store.ts:228`, `src/lib/recurrence-expansion.ts`). Functionally fine on all server versions; RN's expansion file is a line-for-line port of WEB's (diffed: only comments, formatting and the `isServerRecurrenceInstance` pass-through differ).
|
||||
- Fix hint: optional. If adopted, port `recurrence-instances.ts` wholesale and the `isServerRecurrenceInstance` guard in `expandRecurringEvents`.
|
||||
@@ -88,11 +88,6 @@ RN has a solid read path (month/week/agenda, shared-calendar namespacing, per-vi
|
||||
- What RN does: `EventDetailSheet` has an `onDuplicate` prop that `CalendarScreen` never passes (`src/components/calendar/EventDetailSheet.tsx:59, 336-342`); no export/copy/note.
|
||||
- Fix hint: wire `onDuplicate` (clone like WEB `handleDuplicateFromDetail`), add "Share .ics" via `expo-sharing` + a port of `eventToICS`, add "Copy link" with `Clipboard`.
|
||||
|
||||
- [ ] **Quick natural-language event input** — `P3` — `missing` — deferred: low priority, needs a port of the webmail's quick-event parser and a text field above the month grid
|
||||
- What WEB does: `QuickEventInput` in the toolbar (ref `components/calendar/quick-event-input.tsx`, `calendar-toolbar.tsx`).
|
||||
- What RN does: none.
|
||||
- Fix hint: low priority; could reuse WEB's parser in a text field above the month grid.
|
||||
|
||||
### Invitations / scheduling
|
||||
|
||||
- [x] **Invitations are never sent: participants built with retired `sendTo`, no organizer, no `organizerCalendarAddress`** — `P1` — `bugfix-parity` — fixed in 5dc4070
|
||||
@@ -174,7 +169,7 @@ RN has a solid read path (month/week/agenda, shared-calendar namespacing, per-vi
|
||||
|
||||
### Views / navigation / locale
|
||||
|
||||
- [ ] **Entire calendar UI is hard-coded English although RN has i18n** — `P2` — `missing` — partial in 0f80e98: screen, month/week/agenda views, event card/sheet/modal, drawer, scope dialog, settings, banner and calendar/share sheets use `t()` + date-fns locales; TasksSheet (09c24fa) and `formatReminder` (2abea97) localized too; deferred: ICalImportSheet, ICalSubscriptionSheet and RecurrenceEditor still hard-code English
|
||||
- [ ] **Entire calendar UI is hard-coded English although RN has i18n** — `P2` — `missing` — partial in 0f80e98: screen, month/week/agenda views, event card/sheet/modal, drawer, scope dialog, settings, banner and calendar/share sheets use `t()` + date-fns locales; TasksSheet (09c24fa), `formatReminder` (2abea97) and ICalImportSheet (b7f7110) localized too; deferred: ICalSubscriptionSheet and RecurrenceEditor still hard-code English
|
||||
- What WEB does: everything through `next-intl` (`t('calendar.*')`), month/day names and popover dates localized (changelog 1.6.x "Localize event start date in detail popover and event modal").
|
||||
- What RN does: `src/i18n/index.ts` + `useLocaleStore` are used by 18 other files, but none of `src/components/calendar/*`, `src/screens/CalendarScreen.tsx` or `CalendarSettings.tsx` import it; all labels ("Today", "No events", "Does not repeat", "Going?", RSVP labels, drawer titles, etc.) and `date-fns` `format()` calls without a `locale` are English (`CalendarScreen.tsx:408, 448, 504, 640-641`, `EventModal.tsx:60-67, 326, ...`, `MonthView.tsx:25-26`, `AgendaView.tsx:33-37`).
|
||||
- Fix hint: add a `calendar.*` namespace to `locales/*.json` (WEB's keys can be copied) and pass a date-fns locale from the locale store into `format()`.
|
||||
@@ -199,7 +194,7 @@ RN has a solid read path (month/week/agenda, shared-calendar namespacing, per-vi
|
||||
- What RN does: `CalendarSettings` shows "Hover preview" with delay options and nothing consumes `calendarHoverPreview` (`src/components/settings/CalendarSettings.tsx:87-102`; no other reference in `src/`).
|
||||
- Fix hint: remove the setting row (keep the store key for settings-sync compatibility or drop it).
|
||||
|
||||
- [x] **No day view (falls back to agenda); tapping a month day does not open the day** — `P3` — `partial` — fixed in 0f80e98 ("Day" hidden from the mobile default-view options; a synced "day" shows as Agenda)
|
||||
- [x] **No day view (falls back to agenda); tapping a month day does not open the day** — `P3` — `partial` — fixed in 0f80e98 ("Day" hidden from the mobile default-view options; a synced "day" shows as Agenda); a real day view is back since de3d697, with the week and month views scrolling freely (de3d697, caf61ec, #759) and an endless agenda (71522b9)
|
||||
- What WEB does: day view with time grid; on mobile, tapping a date in month view switches to day view and offers "back to month" (ref `components/calendar/calendar-app.tsx:461-470`, `calendar-day-view.tsx`).
|
||||
- What RN does: `calendarDefaultView === 'day'` maps to agenda (`src/screens/CalendarScreen.tsx:131-137`); month view shows a list below the grid instead. Acceptable UX, but the settings option "Day" is misleading.
|
||||
- Fix hint: either reuse `WeekView` with a single-day `weekDays` array as a day grid, or hide "Day" from the RN default-view options.
|
||||
@@ -219,9 +214,9 @@ RN has a solid read path (month/week/agenda, shared-calendar namespacing, per-vi
|
||||
- What RN does: `formatRange` uses `eventTimeRange` -> `getEventEndDate` (exclusive), so a single-day all-day event on Mar 1 prints "Mar 1 – Mar 2" and a two-day one "Mar 1 – Mar 3" (`src/components/calendar/EventDetailSheet.tsx:63-74`, `src/lib/calendar-utils.ts:84-90`).
|
||||
- Fix hint: for `allDay` use `getEventDisplayEndDate(event)` before comparing/formatting.
|
||||
|
||||
- [ ] **Deep links to calendar/event** — `P3` — `missing` — deferred: no `linking` config exists yet (mail area plumbing); `Calendar` route still takes no params
|
||||
- [ ] **Deep links to calendar/event** — `P3` — `missing` — partly: incoming links are parsed since 5802cae (`src/navigation/linking.ts`), and an event link opens the event since 9282ee6; a date link only opens the Calendar tab
|
||||
- What WEB does: `parseCalendarPath`/`buildCalendarPath` handle `/calendar/<view>/<date>?event=` (ref `lib/deep-links.ts:272-300`, changelog 1.8.3 "Deep links for mail, calendar, contacts, files").
|
||||
- What RN does: `Calendar: undefined` route params (`src/navigation/types.ts:33`); no linking config for calendar. Only matters once RN adds a `linking` config; the mail area may already cover the plumbing.
|
||||
- What RN does: `Calendar: undefined` route params (`src/navigation/types.ts:33`); `handleDeepLink` hands an event to the Calendar tab through `pending-calendar-open`, the same path a tapped reminder takes, but ignores the date.
|
||||
- Fix hint: accept `{ date?, eventId? }` params on the Calendar route and open the detail sheet.
|
||||
|
||||
### Data loading / sync
|
||||
@@ -251,28 +246,28 @@ RN has a solid read path (month/week/agenda, shared-calendar namespacing, per-vi
|
||||
- What RN does: `createEvent` merges the `/set` echo over the payload (`src/api/calendar.ts:272-274`) and appends without expansion (`src/stores/calendar-store.ts:286-304`).
|
||||
- Fix hint: after create, `CalendarEvent/get` the id (RN `getEvents([id])`) and, if `recurrenceRules` is set, run `expandRecurringEvents` for `loadedRange` or call `refresh()`.
|
||||
|
||||
- [ ] **Push: FCM subscription excludes `CalendarEvent`/`Calendar` types** — `P3` — `partial` — deferred: local reminders (8e1d03c) are scheduled from foreground fetches only; adding the calendar types to the FCM subscription lives in `src/lib/push-notifications.ts` (push area) and would need a background refresh + reschedule hook
|
||||
- [ ] **Push: FCM subscription excludes `CalendarEvent`/`Calendar` types** — `P3` — `partial` — deferred: local reminders (8e1d03c) are resynced at launch, on `Calendar`/`CalendarEvent` state changes and on return to the foreground (4b903ed), but nothing refreshes them in the background; adding the calendar types to the push subscription lives in `src/lib/push-notifications.ts` (push area) and would need a background refresh + reschedule hook
|
||||
- What WEB does: push subscription covers calendar types so the sidebar refreshes on external changes (FEATURES.md "JMAP push keeps everything in sync").
|
||||
- What RN does: foreground EventSource/polling dispatches to `useCalendarStore.handleStateChange` (`App.tsx:390-397`, `src/stores/calendar-store.ts:261-284`) — at parity while the app is open; the background FCM subscription lists only `['Email','EmailDelivery','Mailbox']` (`src/lib/push-notifications.ts:104`), so calendar changes never wake the app. Acceptable unless local reminders are added.
|
||||
- What RN does: foreground EventSource/polling dispatches to `useCalendarStore.handleStateChange` (`App.tsx:390-397`, `src/stores/calendar-store.ts:261-284`) — at parity while the app is open; the background push subscription carries only `['EmailDelivery']` since 00faceb (`src/lib/push-notifications.ts`), so calendar changes never wake the app.
|
||||
- Fix hint: leave as is unless background calendar refresh becomes needed.
|
||||
|
||||
### Alerts / notifications
|
||||
|
||||
- [x] **Reminders never fire on mobile (no alert scheduler, no local notifications)** — `P2` — `missing` — fixed in 8e1d03c (expo-notifications local notifications scheduled from the store for the next 7 days, calendar default alerts honoured, cancelled events / completed tasks / acknowledged alerts skipped)
|
||||
- [x] **Reminders never fire on mobile (no alert scheduler, no local notifications)** — `P2` — `missing` — fixed in 8e1d03c (expo-notifications local notifications scheduled from the store for the next 7 days, calendar default alerts honoured, cancelled events / completed tasks / acknowledged alerts skipped); moving the calendar view cancelled them until 4b903ed (audit B17), and a tapped reminder opens its event since 83db70a
|
||||
- What WEB does: `useCalendarAlerts` polls every 60 s, proactively fetches the next 24 h, computes fire times from `alerts` (offset relative to start/end, absolute triggers, calendar default alerts via `useDefaultAlerts`), dedupes through a persisted acknowledged store, plays a sound and toasts; task due alerts too; cancelled events muted (#572) (ref `lib/calendar-alerts.ts:39-201`, `hooks/use-calendar-alerts.ts:21-158`, `stores/calendar-notification-store.ts`).
|
||||
- What RN does: `src/lib/calendar-alerts.ts` only converts offsets <-> reminder presets (1-103); the `calendarNotificationsEnabled`/`calendarNotificationSound` settings are shown in `NotificationSettings.tsx:52-53, 235-240` but nothing reads them; `package.json` has no `expo-notifications`. Users who set "15 minutes before" get nothing.
|
||||
- Fix hint: add `expo-notifications`, port `computeFireTime`/`getPendingAlerts`, and schedule local notifications for the next N days on every `fetchEvents` (cancel + reschedule by event id); honour `useDefaultAlerts` via `Calendar.defaultAlertsWithTime/WithoutTime` (add those to `Calendar/get` properties).
|
||||
|
||||
### Tasks
|
||||
|
||||
- [x] **Task sheet supports only title + due date; no edit, description, priority, due time, progress states, alerts, filters, "show on calendar"** — `P2` — `partial` — fixed in 09c24fa (edit, description, priority, due date + time, calendar move, all/pending/completed/overdue filters, overdue-first sort, `needs-action` <-> `completed` with `progressUpdated`, due tasks overlaid on the grid; task alerts still come only from the server data)
|
||||
- [x] **Task sheet supports only title + due date; no edit, description, priority, due time, progress states, alerts, filters, "show on calendar"** — `P2` — `partial` — fixed in 09c24fa (edit, description, priority, due date + time, calendar move, all/pending/completed/overdue filters, overdue-first sort, `needs-action` <-> `completed` with `progressUpdated`, due tasks overlaid on the grid; task alerts still come only from the server data); completing failed on Stalwart, which rejects `progressUpdated`, until dcdda5e (audit B12, #958)
|
||||
- What WEB does: task modal with description, due date + optional time, priority (none/high/medium/low -> 1-9), progress, calendar, alert; list filters all/pending/completed/overdue, overdue-first sort, `showTasksOnCalendar` overlays due tasks on the grid; completion toggles `needs-action` <-> `completed` with `progressUpdated` (ref `components/calendar/task-modal.tsx:55-140`, `task-list-view.tsx:80-110`, `stores/task-store.ts:366, 442-452`).
|
||||
- What RN does: `TasksSheet` creates `{ title, progress, due }` only, no tap-to-edit, delete + toggle only (`src/components/calendar/TasksSheet.tsx:117-135, 210-235`); un-completing sets `in-process` (`src/stores/calendar-store.ts:418-426`); `showTasksOnCalendar` (`settings-store.ts:174`) is unused; tasks are never drawn in month/week/agenda.
|
||||
- Fix hint: add a task edit sheet mirroring `task-modal.tsx`, filter chips, and render due tasks as all-day chips when `showTasksOnCalendar`.
|
||||
|
||||
### Settings
|
||||
|
||||
- [ ] **No custom time-zone setting (#755); no birthday-calendar colour; no "Day" view on mobile (see above)** — `P3` — `missing` — time-zone setting (`calendarTimeZone`, used for saved events and JMAP queries) done in 58a6f45 + 0f80e98; deferred: birthday-calendar colour setting
|
||||
- [ ] **No custom time-zone setting (#755); no birthday-calendar colour; no "Day" view on mobile (see above)** — `P3` — `missing` — time-zone setting (`calendarTimeZone`, used for saved events and JMAP queries) done in 58a6f45 + 0f80e98; the editors and views work in calendar-zone wall time since a8e6f6e and 8af163a, and floating task dues since 1a651a9; deferred: birthday-calendar colour setting
|
||||
- What WEB does: `timeZone: 'auto' | IANA` overriding browser detection, used for display (`displayNow`, `toDisplayDate`) and for the `timeZone` on saved events (ref `lib/timezone.ts:29-75`, `stores/settings-store.ts:317, 553`, changelog 1.9.2 #755); `birthdayCalendarColor` (`settings-store.ts:377, 609`).
|
||||
- What RN does: device zone only (`src/api/calendar.ts:15-21`); birthday colour fixed (`src/lib/birthday-calendar.ts:7`).
|
||||
- Fix hint: add `timeZone` to the settings store (synced with WEB's key so it round-trips through settings sync), pass it as the JMAP `timeZone` arg and into saved events; convert display via `Intl` like WEB's `getWallClock`.
|
||||
@@ -304,3 +299,4 @@ RN has a solid read path (month/week/agenda, shared-calendar namespacing, per-vi
|
||||
- `webcal:` / `mailto:` protocol-handler registration and import-or-subscribe prompt for detected webcal links.
|
||||
- Agenda plugin sidecar, plugin event-action slot (Jitsi), Pro tab deep-link URL writing.
|
||||
- Refresh-gesture interception (F5/Ctrl-R) — RN has pull-to-refresh.
|
||||
- Quick natural-language event input: the webmail has no parser for it either (dropped from the findings by the 2026-09 audit).
|
||||
|
||||
@@ -190,29 +190,29 @@ RN covers the visible surface reasonably well (list with alphabetical index, det
|
||||
### Composer autocomplete
|
||||
|
||||
- [ ] **Autocomplete lacks groups, recent recipients (Sent), on-demand server search and directory principals** — `P2` — `missing`
|
||||
- store-side done in 26a34d5 (`getAutocomplete` with group entries + recent recipients, `loadRecentRecipients`/`searchRecipients` fed by `src/api/recent-recipients.ts`, `getGroupRecipients`, #672 normalisation); wiring `ComposeScreen`/`ParticipantInput` belongs to the composer agent; directory principals not ported.
|
||||
- store-side done in 26a34d5 (`getAutocomplete` with group entries + recent recipients, `loadRecentRecipients`/`searchRecipients` fed by `src/api/recent-recipients.ts`, `getGroupRecipients`, #672 normalisation); the composer uses `getAutocomplete` and the recent Sent recipients since 5b72c4d. Still open: the on-demand server search (`searchRecipients` has no caller), directory principals, and the calendar's `ParticipantInput`, which still offers contacts only.
|
||||
- What WEB does: `getAutocomplete` merges contacts, group entries (as one chip with `memberCount`), RFC 9670 directory principals and recent Sent recipients, deduped and sanitised (#672) (`stores/contact-store.ts:622-707, 23-49`; `loadRecentRecipients` `:1118-1142` fed from `mail-app.tsx:423`; `searchRecipients` `:1144-1154` used by `components/email/email-composer.tsx:1175-1212`; group chip insertion `:1218-1233`; changelogs 1.6.x "Recipient autocomplete from Sent, with on-demand server search", 1.7.x group chips).
|
||||
- What RN does: suggestions come only from `individuals` matched with `matchesContactSearch` (`src/screens/ComposeScreen.tsx:367-392`); groups are explicitly filtered out and nothing else is consulted. `ParticipantInput` (calendar) likewise flattens contacts only (`src/components/calendar/ParticipantInput.tsx:34-51`).
|
||||
- Fix hint: add `recentRecipients` (scan Sent `to/cc`, 300 msgs) to the contacts store, include groups as a pickable entry that expands to member emails on pick, and reuse `sanitizeDisplayName/splitMailbox`-style cleanup for names that contain a mailbox (#672).
|
||||
|
||||
### Viewer integration
|
||||
|
||||
- [ ] **"Add sender to contacts" from the message view missing** — `P2` — `missing`
|
||||
- [x] **"Add sender to contacts" from the message view missing** — `P2` — `missing` — done in d2ed27f (the viewer's address sheet, `src/components/email/AddressActionSheet.tsx`, offers "Add to contacts" and "Open contact")
|
||||
- contact-side done: `ContactForm { prefill }` seeds email + given/surname (9800ad0), `findContactByEmail` in the store (26a34d5); the sender sheet in `EmailThreadScreen` belongs to the viewer agent.
|
||||
- What WEB does: contact sidebar/popover on sender click with "Add to contacts" that creates a card with given/surname split from the display name (`components/email/email-viewer.tsx:5350-5366, 558-564`); also the mobile recipient popover (#306) and `/contacts/new?addEmail=` deep link (`components/contacts/contacts-app.tsx:182-198`).
|
||||
- What RN does: no contact lookup or "add" action in `EmailThreadScreen` (grep contact/ContactForm: only `SenderAvatar` at `:794`). Only `ContactForm`'s `route.params` support `contactId/addressBookId/asGroup` (`src/navigation/types.ts:24`) — no email/name prefill.
|
||||
- Fix hint: add `prefill?: { email; name }` to the `ContactForm` route, and a sender-tap sheet in `EmailThreadScreen` with "Add to contacts" / "Open contact" (lookup by email in `useContactsStore`).
|
||||
|
||||
- [ ] **Sender-name tap → contact popup missing** — `P3` — `missing`
|
||||
- [x] **Sender-name tap → contact popup missing** — `P3` — `missing` — done in d2ed27f (tapping a sender or recipient opens `AddressActionSheet`, which shows the matching contact and "Open contact")
|
||||
- deferred: viewer agent (same sheet as above; `findContactByEmail` + `ContactDetail { contactId }` are ready).
|
||||
- What WEB does: clicking the sender name opens the contact popup with details/actions (changelog 1.4.x "Show contact popup when clicking the sender name", `email-viewer.tsx:392-455`).
|
||||
- What RN does: none (see previous item).
|
||||
- Fix hint: same sheet as above; show the matching card's phone/org and an "Open" that navigates to `ContactDetail`.
|
||||
|
||||
- [ ] **Contact deep links (`/contacts/<id>[/edit]`, `/contacts/new`)** — `P3` — `missing`
|
||||
- deferred: navigation/deep-link agent; contact routes already exist.
|
||||
- partly: `/contacts/<id>` opens the contact since 5802cae; `/contacts/new` and `/contacts/<id>/edit` only open the Contacts tab.
|
||||
- What WEB does: `parseContactsPath`/`buildContactsPath` (`lib/deep-links.ts:323-329`), applied once on mount (`components/contacts/contacts-app.tsx:182-224`).
|
||||
- What RN does: no `linking` config anywhere (grep `prefixes|LinkingOptions|getInitialURL` in `App.tsx`/`src`: none). Cross-reference the navigation/deep-link agent; contact-side requirement is just `ContactDetail { contactId }` / `ContactForm { contactId }` which already exist (`src/navigation/types.ts:23-24`).
|
||||
- What RN does: `src/navigation/linking.ts` (5802cae) maps `/contacts/<id>` to `ContactDetail { contactId }` and every other contacts path to the tab; it never opens `ContactForm`.
|
||||
|
||||
### vCard import / export (`lib/vcard.ts` vs `src/lib/vcard.ts`)
|
||||
|
||||
@@ -257,7 +257,7 @@ RN covers the visible surface reasonably well (list with alphabetical index, det
|
||||
- Fix hint: in the modal, list `trustedSenders ∪ trustedSenderEmails`, mark book entries, and call `removeFromTrustedSendersBook` (+ local remove) on delete; show the union count.
|
||||
|
||||
- [x] **`trustedSendersAddressBook` setting is dead (`false`, no setter, no toggle) — book is consulted unconditionally** — `P3` — `partial` — fixed in 78114b6
|
||||
- Setting is now `boolean | null`, auto-`true` once the session advertises contacts, with a "Sync trusted senders to address book" toggle. Gating the passive load / write in `EmailBodyView` belongs to the viewer agent.
|
||||
- Setting is `boolean | null` with a "Sync trusted senders to address book" toggle. Until d89e331 it switched itself on for a contacts-capable account, and the viewer then trusted every contact in every book; it now stays off until the user turns it on, and only the Trusted Senders book counts (the webmail still auto-enables it).
|
||||
- What WEB does: `null` until the account proves contacts support, then auto-`true` (`stores/auth-store.ts:380-385`, `stores/settings-store.ts:361,593`); toggle in settings (`content-senders-settings.tsx:77-82`); the book is only loaded/used when enabled (`mail-app.tsx:199-203`, `email-viewer.tsx:1705`).
|
||||
- What RN does: default `false`, no setter (`src/stores/settings-store.ts:106,239`); `EmailBodyView` loads the book regardless and ORs `trustedSenderEmails` into the trust check (`:480-485, 513-516`); the `addressBookEmails` branch (treats *every* contact as trusted) is gated on the dead flag (`:486-498`) so it never runs.
|
||||
- Fix hint: either drop the flag and the dead `addressBookEmails` branch, or expose a "Sync trusted senders to address book" toggle that gates the passive load and the `addToTrustedSendersBook` write.
|
||||
@@ -278,7 +278,7 @@ RN covers the visible surface reasonably well (list with alphabetical index, det
|
||||
- What RN does: lower-cases the whole string and stores `emails` only (`src/stores/contacts-store.ts:308-321`); a `Name <email>` string (e.g. typed in settings) becomes an invalid address.
|
||||
- Fix hint: reuse the WEB regex.
|
||||
|
||||
- [ ] **Auto-trust reply recipients missing** — `P3` — `missing`
|
||||
- [x] **Auto-trust reply recipients missing** — `P3` — `missing` — done in 5b72c4d (area 04)
|
||||
- deferred: composer agent; `addToTrustedSendersBook` now accepts `Name <email>` and never races the passive load (26a34d5).
|
||||
- What WEB does: on reply/replyAll every To/Cc recipient is added to the trusted book (`components/email/email-composer.tsx:2284-2292`; changelog 1.4.x "Auto-add recipients to trusted senders when replying").
|
||||
- What RN does: `ComposeScreen` has no trusted-sender code (grep `trusted`: none).
|
||||
@@ -291,16 +291,14 @@ RN covers the visible surface reasonably well (list with alphabetical index, det
|
||||
- What RN does: filters `useCalendarStore().events` (`src/components/contacts/ContactActivity.tsx:90-91, 127-142`), i.e. only the range the calendar tab happened to load; on a fresh start it is empty.
|
||||
- Fix hint: call the RN calendar API for `[now, now+365d]` with a participant filter, falling back to the cache.
|
||||
|
||||
- [ ] **Print contact** — N/A on mobile (share vCard exists). Listed under N/A.
|
||||
|
||||
## Verified at parity (brief list, so the fixer knows what NOT to redo)
|
||||
- Address book list/create/rename/delete with `myRights` checks (`src/components/settings/ContactsSettings.tsx`, `AddressBookPickerSheet`); inline "New address book…" in the move sheet (WEB #415).
|
||||
- Move contacts between books (single from detail, bulk from list) via `addressBookIds` patch (`contacts-store.ts:237-241`) — RN equivalent of WEB drag-and-drop.
|
||||
- Group by first letter with sticky headers + settings toggle (`ContactsScreen.tsx:158-162`, `ContactsSettings.tsx:132-137`); sort by display name.
|
||||
- Group by first letter with sticky headers + settings toggle (`ContactsScreen.tsx:158-162`, `ContactsSettings.tsx:132-137`); sort by display name, or by last name since 1827748 (#963); the sort no longer re-runs on every search keystroke since e6be892.
|
||||
- Search over name/emails/phones/orgs (RN also matches keywords).
|
||||
- Detail screen renders every WEB field block: emails/phones with contexts+features+labels, addresses (components, flat, timezone), org/units/titles/roles, anniversaries with age, gender/pronouns, languages, personalInfo, online services, calendar URIs, crypto keys, categories, relatedTo, notes, address book names, created/updated; quick actions call/SMS/mail/share; copy via long-press.
|
||||
- Contact form: prefix/given/middle/surname/suffix, nickname, display name (`name.full`), multiple emails/phones with context + phone feature pills, addresses as RFC 9553 components, multiple orgs (org/department/title/role), anniversaries with native date picker, online services, personalInfo w/ level, gender+pronouns, calendar URIs, categories (chips + suggestions from existing keywords), multiple notes, photo pick/remove, address book pick, dirty-state discard confirm, email regex validation, `media: {}` when photo removed (equivalent of WEB `media: null`).
|
||||
- `name.full` display fallback (#179), `getContactPrimaryEmail` with `pref`.
|
||||
- `name.full` display fallback (#179), `getContactPrimaryEmail` with `pref`. `name.full` (the vCard FN) is written on every form, "add sender" and import save since a734d0a (#430).
|
||||
- Group detail: member list resolved via id/uid/`urn:uuid:` (`contacts-store.ts:401-415`), add members via `ContactPickerSheet`, remove member, delete group, email all.
|
||||
- Tags (keywords) = WEB categories: drawer section with counts, filter by tag, bulk assign via `TagAssignSheet`, per-contact edit in form.
|
||||
- vCard import sheet: file picker, 5 MB cap, parse, duplicate detection by email (`detectDuplicates` identical), select all/none, target book, result count; export all (settings) and single (detail) with share sheet; multi-vCard files; QUOTED-PRINTABLE + charset decode (RN has a Hermes-safe `decodeBytes`), RFC 6868 params, KIND/MEMBER, PHOTO/LOGO/SOUND, RFC 6474/6715/8605/9554 properties.
|
||||
|
||||
@@ -32,7 +32,7 @@ Filters: RN carries a byte-for-byte port of WEB's Sieve parser/generator/tests a
|
||||
- What RN does: the `filtersExpandedView` toggle exists (`src/components/settings/FilterSettings.tsx:350-355`) but only lifts the `numberOfLines` clamp on the same one-line summary (`:268`, `:292`); `if`/`then`/`match_all_conditions` locale keys are unused.
|
||||
- Fix hint: add a `VisualRuleSummary` component rendering rows of chips (all conditions, all actions) when `expandedView` is true.
|
||||
|
||||
- [ ] **Per-account (shared/group) filters not supported** — `P3` — `missing` (depends on whether RN gets managed shared-account settings at all) — deferred: RN has no managed/shared-account settings yet
|
||||
- [x] **Per-account (shared/group) filters not supported** — `P3` — `missing` (depends on whether RN gets managed shared-account settings at all) — done in a86cd1d (tap a shared account in Account settings to manage its filters; the rule editor offers that account's folders)
|
||||
- What WEB does: `filter-store` tracks `availableAccounts`/`selectedAccountId` and every Sieve call takes an `accountId` (`stores/filter-store.ts:62-134`); `FilterSettings` scopes to `managedAccountId` and fetches that account's mailboxes for the move target list (`components/settings/filter-settings.tsx:188-232`, `248-255`); client `getSieveAccounts()` (`lib/jmap/client.ts:4465-4469`).
|
||||
- What RN does: `src/api/sieve.ts:20-23` always uses the primary Sieve account; store has no account selection (`src/stores/filter-store.ts:32`). No `managedAccount` concept exists in RN (`grep -ri managedAccount src` → none).
|
||||
- Fix hint: only if the Accounts area adds shared-account management: thread an optional `accountId` through `api/sieve.ts` and the store's `fetchFilters/saveFilters`.
|
||||
@@ -42,10 +42,6 @@ Filters: RN carries a byte-for-byte port of WEB's Sieve parser/generator/tests a
|
||||
- What RN does: `src/lib/sieve/__tests__/{parser,generator,external-rules}.test.ts` start with a UTF-8 BOM and contain "→" (double-encoded arrow) at e.g. `external-rules.test.ts:257`, `:343`, `generator.test.ts:369`, `parser.test.ts:104`. Cosmetic, but it shows the files were copied through a wrong encoding; re-copy from WEB when porting the 1.7.3 changes and add tests for list values / `:mime`.
|
||||
- Fix hint: rewrite the three files as UTF-8 without BOM from the WEB copies.
|
||||
|
||||
- [ ] **Raw Sieve editor lacks line-number gutter / Tab-inserts-spaces** — `P3` — `partial` (mostly N/A on mobile)
|
||||
- What WEB does: gutter with line numbers, Tab inserts two spaces (`components/filters/sieve-editor-modal.tsx:61-72`, `101-128`).
|
||||
- What RN does: shows "N lines" below the editor (`src/components/filters/SieveEditorSheet.tsx:107`). Acceptable on mobile; listed so nobody re-audits it.
|
||||
|
||||
### Vacation responder
|
||||
|
||||
- [x] **fromDate/toDate sent without timezone designator (not a JMAP UTCDate)** — fixed in b538a0e — `P2` — `rn-only-bug` — not verified live against stw-test; RFC 8621 UTCDate semantics implemented
|
||||
@@ -73,7 +69,7 @@ Filters: RN carries a byte-for-byte port of WEB's Sieve parser/generator/tests a
|
||||
- What RN does: `VacationSettings.tsx` never calls `useLocaleStore`; "Vacation Responder", "Date Range", "Saved", warnings etc. are literals (`:57-59`, `:72-74`, `:88`, `:101-158`). RN `locales/en/common.json` already contains `settings.vacation.{title,description,status,date_range,message,preview,save,saving,warnings}`.
|
||||
- Fix hint: wire `t('settings.vacation.…')` like `FilterSettings.tsx` does.
|
||||
|
||||
- [ ] **Vacation fetch/save on a shared account** — `P3` — `missing` (same dependency as the filters item) — deferred: same dependency (no managed-account concept in RN)
|
||||
- [x] **Vacation fetch/save on a shared account** — `P3` — `missing` (same dependency as the filters item) — done in a86cd1d
|
||||
- What WEB does: `fetchVacationResponse(client, managedAccountId)` (`components/settings/vacation-settings.tsx:52-56`, `121-128`).
|
||||
- What RN does: always `jmapClient.accountId` (`src/api/vacation.ts:27`, `:43`).
|
||||
|
||||
@@ -114,7 +110,7 @@ Filters: RN carries a byte-for-byte port of WEB's Sieve parser/generator/tests a
|
||||
- What RN does: `getDocumentAsync({ multiple: false })` (`src/screens/FilesScreen.tsx:357-360`); uploading the same name twice creates two nodes with the same name.
|
||||
- Fix hint: `multiple: true` + sequential upload loop; reuse WEB's `getUniqueName` against `visibleFiles`.
|
||||
|
||||
- [ ] **No in-app preview (image/text/markdown/PDF/audio/video/eml)** — `P2` — `missing` — deferred: the OS viewer/share sheet stays the mobile answer for now; no inline viewer ported
|
||||
- [x] **No in-app preview (image/text/markdown/PDF/audio/video/eml)** — `P2` — `missing` — done in 409cdb1 (images, text and source files, `.eml`, and PDFs on iOS open in the reader's preview; PDFs on Android, audio, video, HTML and Markdown still go to another app)
|
||||
- What WEB does: `ImagePreviewModal` and `FilePreviewModal` (text, markdown, PDF incl. mobile pdf.js viewer, audio, video, EML) chosen via `getFilePreviewKind` (`lib/file-preview.ts:29-68`; `components/files/file-preview-modal.tsx`, `image-preview-modal.tsx`, `pdf-mobile-viewer.tsx`; `files-app.tsx:487-495`, `690-708`).
|
||||
- What RN does: tapping a file calls `shareAttachment` which downloads to cache and hands off to the OS share/viewer sheet (`src/screens/FilesScreen.tsx:248-258`; `src/lib/email-export.ts` "preview" variant). Works but there is no inline viewer and no "recent files" tracking.
|
||||
- Fix hint: reuse whatever attachment viewer the mail area has (image modal / WebView for text+PDF); N/A if the team decides the OS viewer is the mobile answer - then close this item.
|
||||
@@ -147,10 +143,10 @@ Filters: RN carries a byte-for-byte port of WEB's Sieve parser/generator/tests a
|
||||
- What WEB does: on first listing, reparents nodes named with `/`, `∕`, `⁄`, `/` separators and replaces blob-backed "dir marker" files with real folders, with a progress overlay (`files-app.tsx:245-263`, `710-734`).
|
||||
- What RN does: reads only the real hierarchy (`src/api/files.ts:6-14` comment acknowledges WEB migrates). A user who never opens WEB sees legacy files as flat names at the root. Acceptable if WEB is always used at least once; otherwise port `migrateLegacyFlatNodes`.
|
||||
|
||||
- [ ] **Deep link `/files/<folder>` and `?preview=` not handled** — `P3` — `missing` (changelog 1.8.1 "Deep links for mail, calendar, contacts, files") — deferred: belongs to the navigation area (RN has no linking config yet)
|
||||
- [ ] **Deep link `/files/<folder>` and `?preview=` not handled** — `P3` — `missing` (changelog 1.8.1 "Deep links for mail, calendar, contacts, files") — partly: `/files` links open the Files tab since 5802cae; folder paths and `?preview=` are ignored
|
||||
- What WEB does: `parseFilesPath`/`buildFilesPath` walk the drive to the folder and open the preview (`components/files/files-app.tsx:190-278`; `lib/deep-links.ts:381`).
|
||||
- What RN does: no React Navigation `linking` config anywhere (`grep -rn "linking" src App.tsx` → none); `Files: undefined` in `src/navigation/types.ts:35`.
|
||||
- Fix hint: belongs to the navigation area; when linking is added, map `/files/*` to `Files` with a `path` param and walk `allNodes` by names.
|
||||
- What RN does: `src/navigation/linking.ts` (5802cae) turns any `/files/...` link into `{ kind: 'files' }` and opens the tab; `Files: undefined` in `src/navigation/types.ts:35`.
|
||||
- Fix hint: parse the rest of the path in `linking.ts`, pass it to `Files` as a `path` param and walk `allNodes` by names.
|
||||
|
||||
- [x] **Hard-coded English throughout Files screen and ShareSheet** — fixed in c0e1d6b — `P3` — `rn-only-bug`
|
||||
- What WEB does: everything via `files.*` locale keys (`locales/en/common.json` `files` has ~120 keys incl. `share`, `shared`, `shared_with_me`, `shared_by`).
|
||||
@@ -161,7 +157,7 @@ Filters: RN carries a byte-for-byte port of WEB's Sieve parser/generator/tests a
|
||||
- What WEB does: persistent yellow "stability_warning" above the browser (`files-app.tsx:617-620`; changelog 1.4.10 "Update file feature disabled messages and add stability warnings").
|
||||
- What RN does: nothing. Optional; product call.
|
||||
|
||||
- [ ] **Storage quota not surfaced in Files** — `P3` — `missing` (partly N/A) — N/A: quota belongs to the Account/Settings area, Files shows none (like WEB's mobile layout)
|
||||
- [x] **Storage quota not surfaced in Files** — `P3` — `missing` (partly N/A) — done in 490355c: the quota shows in Account settings; Files shows none (like WEB's mobile layout)
|
||||
- What WEB does: passes `quota` from the email store into the navigation rail beside Files (`files-app.tsx:55`, `:569`); changelog 1.7.5 "Storage quota not shown with Stalwart (#577)".
|
||||
- What RN does: `FilesScreen` shows no quota. Whether RN shows quota anywhere is outside this area; if it exists in Settings/Account, this is N/A.
|
||||
|
||||
@@ -169,17 +165,17 @@ Filters: RN carries a byte-for-byte port of WEB's Sieve parser/generator/tests a
|
||||
- `repos/react-native/README.md:30` lists filters, S/MIME, plugins, themes and file storage as stubs; filters, vacation and files are real implementations (`src/api/files.ts`, `src/screens/FilesScreen.tsx`, `src/components/files/ShareSheet.tsx`, `src/api/__tests__/files.test.ts`). Update the README when the items above land.
|
||||
|
||||
## Verified at parity (brief list, so the fixer knows what NOT to redo)
|
||||
- Sieve parser/generator/types are a faithful port up to 1.7.2: `diff -w --strip-trailing-cr lib/sieve/parser.ts repos/react-native/src/lib/sieve/parser.ts` shows only the multi-value/attachment hunks (and `debug.warn` → `console.warn`). Included and identical: `@metadata` JSON round-trip, external-rule parsing with origin labels (Roundcube/Nextcloud/etc., changelog 1.4.14 #201), Nextcloud marker regions preserved verbatim, `# Rule:` dedupe of Bulwark blocks that failed to re-parse (1.7.0 "literal braces" fix, `parser.ts` `findBodyOpenBrace` + `filteredExternal`), vacation-only script detection, `externalRequires` merge, `INBOX` canonical path (1.7.1 #313: `FilterRuleModal.tsx:57`), all 10 action types, `computeRequires` (fileinto/copy/imap4flags/reject/body/vacation), `stop` folding into `stopProcessing`.
|
||||
- Sieve parser/generator/types are a faithful port up to 1.7.2: `diff -w --strip-trailing-cr lib/sieve/parser.ts repos/react-native/src/lib/sieve/parser.ts` shows only the multi-value/attachment hunks (and `debug.warn` → `console.warn`). Included and identical: `@metadata` JSON round-trip, external-rule parsing with origin labels (Roundcube/Nextcloud/etc., changelog 1.4.14 #201), Nextcloud marker regions preserved verbatim, `# Rule:` dedupe of Bulwark blocks that failed to re-parse (1.7.0 "literal braces" fix, `parser.ts` `findBodyOpenBrace` + `filteredExternal`), vacation-only script detection, `externalRequires` merge, `INBOX` canonical path (1.7.1 #313: `FilterRuleModal.tsx:57`), all 10 action types, `computeRequires` (fileinto/copy/imap4flags/reject/body/vacation), `stop` folding into `stopProcessing`. Since then the webmail writes "Keep" as `fileinto "INBOX"` (62465e1e, #1027) and added the vacation `include`, `:mailboxid` targets, `redirect :copy` and the spam guard; ported in f63d739, d53bfec, 3f88868 and 6c31c8d.
|
||||
- Filter store semantics identical: skip server-managed `vacation` script (1.4.10), keep activation via `onSuccessActivateScript` on create/update (1.4.10), external/opaque rules read-only, bulwark rules kept contiguous before external ones, `addRule/updateRule/deleteRule/reorderRules/toggleRule`, rollback on failed save, opaque banner with "Open raw editor" / two-step "Reset to visual builder", `SieveScript/validate` via uploaded blob with error-method handling, `createSieveScript('filters')`.
|
||||
- Filter rule editor: name/match type/conditions (from/to/cc/subject/header+name/size/body)/comparators/actions (move & copy folder picker from own non-shared mailboxes, forward, reject text, add_label from keywords store, mark read/star/discard/keep/stop)/stop-processing; empty-name/condition/action validation; comparator reconciliation on field change; default first mailbox for move/copy. Reorder via up/down chevrons instead of drag (fine on mobile). Delete uses a native confirm. Mailboxes are loaded at login (`src/stores/auth-store.ts:84`), so WEB fix #485 (load mailboxes when Filters opened directly) is not needed.
|
||||
- Raw Sieve editor: warning banner, Validate with server errors, two-step confirm save, re-seeds on open, atomic `setOpaqueScript`.
|
||||
- Vacation: `VacationResponse/get|set` on singleton with `core+mail+vacationresponse` using, default object when the list is empty, capability gating (`isVacationSupported`), enable toggle + status pill, subject, plain-text body, preview, end-before-start warning, empty-body warning, error surfacing.
|
||||
- Files API: folder detection `blobId == null` (`src/api/files.ts:12-14`), `FileNode/get ids:null` (not `/query`, which drops folders), `shareWith/myRights` requested explicitly, `principals:owner` in `using`, real-hierarchy folder create without blob/type/size, cascade delete with `onDestroyRemoveChildren`, MIME-type >30 chars → `application/octet-stream` (1.4.12), cross-account "shared with me" aggregation with `accountId:nodeId` namespacing and owner-routed download URLs, shared subtrees browsable and writes hidden inside them, `Share2`/`Users` badges, ShareSheet with the same read/readWrite/manager presets as WEB `FILE_PRESETS`, custom-rights detection, principal search excluding self, revoke, refresh after change; unit tests in `src/api/__tests__/files.test.ts`.
|
||||
- Files API: folder detection `blobId == null` (`src/api/files.ts:12-14`), `FileNode/get ids:null`, which Stalwart caps at `maxObjectsInGet` (500), continued since 4eb8c47 by paging `FileNode/query` (which lists folders too since Stalwart 0.16.6) and batched gets (#1069), `shareWith/myRights` requested explicitly, `principals:owner` in `using` only when advertised (sharing is gated on `principals` since caea3d0), real-hierarchy folder create without blob/type/size, cascade delete with `onDestroyRemoveChildren`, MIME-type >30 chars → `application/octet-stream` (1.4.12), cross-account "shared with me" aggregation with `accountId:nodeId` namespacing and owner-routed download URLs, shared subtrees browsable and writes hidden inside them, `Share2`/`Users` badges, ShareSheet with the same read/readWrite/manager presets as WEB `FILE_PRESETS`, custom-rights detection, principal search excluding self, revoke, refresh after change; unit tests in `src/api/__tests__/files.test.ts`.
|
||||
- Files UI: list/grid toggle persisted to settings, folder breadcrumb, pull-to-refresh, hidden-file filter, colored/plain icons, long-press multi-select (own nodes only), rename/new-folder prompts rejecting `/`, delete confirmation naming folder cascade, path stack pruned when a folder disappears, Files tab disabled when the capability is missing.
|
||||
|
||||
## N/A on mobile
|
||||
- Drag-and-drop upload / whole-folder upload (`uploadFolder`, `getDroppedFilesAndFolders`), drag-out of files, marquee selection, keyboard shortcuts (Ctrl+A/C/X/V, F2, Delete, Backspace), right-click context menus, resizable folder-tree sidebar, breadcrumb right-click dropdown, `?preview=` window title updates, `window.open` blob preview safety list (`isMimeTypeSafeForInlinePreview`).
|
||||
- WebDAV proxy client (`lib/webdav/client.ts`, `stores/webdav-store.ts`) - WEB itself only uses it for calendar collections now; the Files app is JMAP-only, so RN correctly has no WebDAV code.
|
||||
- Pro-shell cross-account Files picker / `accountFolders` / `__account_root__` breadcrumb sentinel, `filesEnabled` admin policy gate (RN has no policy store), plugin hooks `onSieveScriptGenerate`/`onFiltersSave`.
|
||||
- Drag-to-reorder filter rules (RN uses chevrons), line-number gutter in the Sieve editor.
|
||||
- Drag-to-reorder filter rules (RN uses chevrons), line-number gutter and Tab-inserts-spaces in the Sieve editor (RN shows "N lines" below it).
|
||||
- "Attach from Files" in the composer and "Save attachment to Files" in the viewer: neither client implements these (WEB composer only takes local files, `components/email/email-composer.tsx:1391-1555`; no `createFileNode` call outside the Files store), so nothing to port.
|
||||
|
||||
@@ -12,7 +12,7 @@ Legend: RN? = key exists in RN store; Honored = read by RN app code outside `sto
|
||||
|
||||
| WEB key (default) | RN key (default) | RN? | Honored | Notes |
|
||||
|---|---|---|---|---|
|
||||
| fontSize (medium) | fontSize (medium) | yes | partial | Only `useTypography()` callers scale: EmailListScreen, SidebarDrawer, CalendarSidebarDrawer (`RN: src/theme/dynamic.ts:47`); 89 files import static `typography` from tokens |
|
||||
| fontSize (medium) | fontSize (medium) | yes | partial | Only `useTypography()` callers scale: EmailListScreen and its attachment chips (`RN: src/theme/dynamic.ts:47`); the other files import static `typography` from tokens |
|
||||
| density (regular) | density (regular) | yes | yes | EmailListScreen via `useDensity()` |
|
||||
| animationsEnabled (true) | animationsEnabled (true) | yes | partial | Only via `useAnimDuration` in 3 files; 18 files call `Animated.timing` with fixed durations (UndoSnackbar, SwipeableRow, every sheet) |
|
||||
| messageListOrder / messageListOrderScope (#718) | mailSortAscending (false) | partial | yes | Single boolean; native issue #5. Owner: mail-list agent |
|
||||
@@ -99,7 +99,7 @@ RN toggles that are stored but never read (fix: either wire them or remove the c
|
||||
- What RN does: 25, true, true, true, false, false, right=read/left=archive, 4 English keywords (`RN: src/stores/settings-store.ts:232-325`).
|
||||
- Fix hint: align defaults where the behaviour is the same on both clients (attachment keywords, includeGroupInUnified, autoSelectReplyIdentity, showBirthdayCalendar) before any sync exists, otherwise the first sync flips them for existing users.
|
||||
|
||||
- [ ] **`trustedSendersAddressBook` can never be turned on in RN** — `P3` — `partial`
|
||||
- [x] **`trustedSendersAddressBook` can never be turned on in RN** — `P3` — `partial` — done in 78114b6 (toggle in Content & Senders); since d89e331 it stays off until the user turns it on, where the webmail turns it on by itself
|
||||
- What WEB does: `null` resolves to `true` on first connect when the server has contacts; the Content & Senders tab has a toggle (`components/settings/content-senders-settings.tsx`, `updateSetting('trustedSendersAddressBook'`).
|
||||
- What RN does: default `false` (`RN: src/stores/settings-store.ts:239`), read by `EmailBodyView.tsx:470,489` but `ContentSendersSettings.tsx` has no control for it.
|
||||
- Fix hint: add the toggle; treat undefined as auto-on when `useHasContacts()`.
|
||||
@@ -120,7 +120,7 @@ RN toggles that are stored but never read (fix: either wire them or remove the c
|
||||
- Fix hint: add `resetToDefaults` to the store (`set(DEFAULT_PERSISTED)` + persist), an export via `expo-sharing` and import via `expo-document-picker` using WEB's export shape so files round-trip between clients; add a "refresh cached data" that clears `email-snapshot`/contacts/calendar caches without logging out.
|
||||
|
||||
- [x] **Debug mode / categories are inert local state; no debug/logger abstraction** — fixed in 8deff66 — `P3` — `missing` — src/lib/debug.ts; dead Debug tab removed in 3d0d440
|
||||
- What WEB does: `debugMode`/`debugCategories` persisted and honoured by `lib/debug.ts:8-13`; `lib/error-reporting.ts` funnels error-boundary reports; `lib/logger.ts` server side. Debug tab gated by admin (`settings-app.tsx:766`).
|
||||
- What WEB does: `debugMode`/`debugCategories` persisted and honoured by `lib/debug.ts:8-13`; `lib/logger.ts` server side (`lib/error-reporting.ts`, cited here before, has since been deleted from the webmail). Debug tab gated by admin (`settings-app.tsx:766`).
|
||||
- What RN does: `AboutDataSettings.tsx:61-66,226-244` keeps toggles in `useState` with categories (`sync`, `render`) that exist nowhere; the "Debug" tab is `implemented: false` (`SettingsScreen.tsx:111`); code uses raw `console.warn`.
|
||||
- Fix hint: persist `debugMode`/`debugCategories`, add `src/lib/debug.ts` mirroring WEB's category API, route the `[push]`, `[settings-store]`, `[updates-store]` warnings through it, and drop the dead Debug tab or implement a log viewer.
|
||||
|
||||
@@ -136,7 +136,7 @@ RN toggles that are stored but never read (fix: either wire them or remove the c
|
||||
|
||||
### Push notifications
|
||||
|
||||
- [x] **RN subscribes to `Email` and `Mailbox` state changes, not just `EmailDelivery`** — fixed in 00faceb — `P2` — `bugfix-parity`
|
||||
- [x] **RN subscribes to `Email` and `Mailbox` state changes, not just `EmailDelivery`** — fixed in 00faceb — `P2` — `bugfix-parity` — the `emailPush` map it added was refused for ACL-shared mailboxes, which could leave push off for good, and was re-patched on every launch, until d8046a8 and ccaa24e (audit B18)
|
||||
- What WEB does: `PUSH_TYPES = ['EmailDelivery']` because `Email` fires on every mutation and produced spurious notifications (`lib/web-push.ts:39-44`; changelog 1.5.x "Scope new-mail notifications to genuine inbox deliveries").
|
||||
- What RN does: `PUSH_TYPES = ['Email', 'EmailDelivery', 'Mailbox']` (`RN: src/lib/push-notifications.ts:104`). Every read/flag/move/draft on any client wakes the device through FCM and runs the headless task.
|
||||
- Fix hint: change to `['EmailDelivery']` and bump the subscription (the refresh path only patches `expires`, `:411-429`; add a `types` mismatch check like WEB `:369-383` so existing subscriptions get corrected).
|
||||
@@ -151,7 +151,7 @@ RN toggles that are stored but never read (fix: either wire them or remove the c
|
||||
- What RN does: `identifyAccountFromFcmData` scans payload values for a locally stored `deviceClientId` (`RN: src/lib/push-background-task.ts:67-87`), but the relay sends `kind, accountLabel, accountId, emailIds, changed` (`repos/relay/src/fcm.ts` message.data) - never the subscription id. Result: every push processes every logged-in account serially inside a 30 s headless budget (`BulwarkPushTaskService.kt` timeout), loading each account's JMAP session.
|
||||
- Fix hint: the relay's `accountId` is the JMAP primary account id; persist each account's JMAP account id in `AccountEntry` (`RN: src/stores/account-store.ts:7-19` has none) or in the push registry (`push:accountIds:v1` → map local id → jmap id) at setup time, and match on that. `accountLabel` (= username) is a weaker second key.
|
||||
|
||||
- [x] **No `emailPush` delivery filter (spam still wakes the device)** — fixed in 00faceb — `P2` — `bugfix-parity`
|
||||
- [x] **No `emailPush` delivery filter (spam still wakes the device)** — fixed in 00faceb — `P2` — `bugfix-parity` — the map was refused for ACL-shared mailboxes and never read back until d8046a8 and ccaa24e (audit B18)
|
||||
- What WEB does: when the session advertises `urn:ietf:params:jmap:emailpush` (Stalwart ≥ 0.16.16) the subscription carries a per-account filter `notKeyword:$junk AND inMailboxOtherThan:[junk ids]` (`lib/web-push.ts:53-108,491-497`, commit 63fa2d2d) and re-syncs it in the background on every page load (`:668-688`, `components/push-notification-prompt.tsx:119-126`, 1.9.2).
|
||||
- What RN does: `createPushSubscription` has no `emailPush` parameter (`RN: src/api/push.ts:23-57`); the Inbox-only query hides junk from the notification but the wake-up and JMAP round-trip still happen for every spam delivery.
|
||||
- Fix hint: port `buildEmailPushConfig` + `serverSupportsEmailPush` (session capabilities are in `jmapClient.currentSession.capabilities`), add `emailPush` to `createPushSubscription`/`updatePushSubscription`, and extend `refreshSubscriptionExpires` to patch `types`/`emailPush` when they differ.
|
||||
@@ -176,7 +176,7 @@ RN toggles that are stored but never read (fix: either wire them or remove the c
|
||||
- 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.
|
||||
|
||||
- [ ] **No non-FCM transport (UnifiedPush, native #44/#48)** — `P3` — `missing` — deferred: relay must accept key-less web registrations + a UnifiedPush connector module
|
||||
- [x] **No non-FCM transport (UnifiedPush, native #44/#48)** — `P3` — `missing` — done in ee04e95 (Android: UnifiedPush connector through a distributor app such as ntfy; the relay falls back to unencrypted delivery for distributors without Web Push keys); not in a release yet
|
||||
- What WEB does: N/A (browser push).
|
||||
- What RN does: FCM only (`BulwarkMessagingService.kt`, `push-notifications.ts:120-128`).
|
||||
- Fix hint: UnifiedPush distributors expose an RFC 8030 endpoint, which is exactly what the relay's `/api/push/register/web` already accepts (`repos/relay/src/server.ts` handleRegisterWeb); VAPID/`keys` are optional in UnifiedPush so the relay must accept a record without `p256dh`/`auth` and send unencrypted (or only the id). Client side: `unifiedpush-react-native`/`org.unifiedpush.android.connector` receiving the message and calling the same headless task.
|
||||
@@ -220,7 +220,7 @@ RN toggles that are stored but never read (fix: either wire them or remove the c
|
||||
|
||||
- [ ] **No RTL support (ar/he/fa)** — `P2` — `missing` — done in 990cd84: forceRTL/allowRTL on override + restart hint, isLayoutRTL() helper in src/i18n; deferred: SwipeableRow side swap (mail-list agent's file - call isLayoutRTL() there)
|
||||
- What WEB does: `getLocaleDirection` sets `dir=rtl`, logical CSS, JS popovers flip, RTL-aware swipe (`i18n/direction.ts`; changelog 1.7.0-1.7.3 RTL entries).
|
||||
- What RN does: none of the three RTL locales are shipped; no `I18nManager` usage anywhere; `android:supportsRtl="true"` is set (`RN: AndroidManifest.xml:16`) so the OS would mirror layouts when the device locale is RTL, but the app's own locale override cannot force it, and `SwipeableRow` maps left/right physically.
|
||||
- What RN does: ar, he and fa ship since 990cd84, which also calls `I18nManager.forceRTL`/`allowRTL` when the language override changes (after a restart); mail bodies get `dir="auto"` since 910c503 (audit U4). `SwipeableRow` still maps left/right physically.
|
||||
- Fix hint: when adding ar/he/fa, call `I18nManager.forceRTL(dir==='rtl')` + `allowRTL` on override change (requires reload), audit `paddingLeft/Right` → `paddingStart/End`, and swap swipe actions when `I18nManager.isRTL`.
|
||||
|
||||
- [ ] **Language list not localized and no "auto" date locale** — `P3` — `partial` — deferred: language labels are native names (webmail parity); timeZone landed as calendarTimeZone (calendar agent); dateLocale needs a locale override threaded through src/lib/date-format.ts call sites in other areas
|
||||
@@ -230,9 +230,9 @@ RN toggles that are stored but never read (fix: either wire them or remove the c
|
||||
|
||||
### Themes and appearance
|
||||
|
||||
- [ ] **Font size setting is honoured by 3 of ~90 files** — `P3` — `partial` — deferred: Text.defaultProps is gone on the new architecture; needs the static typography import replaced by useTypography() per screen
|
||||
- [ ] **Font size setting is honoured by the mail list only** — `P3` — `partial` — deferred: Text.defaultProps is gone on the new architecture; needs the static typography import replaced by useTypography() per screen
|
||||
- What WEB does: `--font-size-base` on `:root` scales everything (`stores/settings-store.ts:1264-1274`).
|
||||
- What RN does: `useTypography()` (`RN: src/theme/dynamic.ts:47-68`) is used by EmailListScreen, SidebarDrawer, CalendarSidebarDrawer; the other screens spread the static `typography` from `tokens.ts`, so Small/Large changes almost nothing (the thread reader, compose, settings, calendar, contacts all stay fixed).
|
||||
- What RN does: `useTypography()` (`RN: src/theme/dynamic.ts:47-68`) is used by EmailListScreen and its attachment chips only; the other screens spread the static `typography` from `tokens.ts`, so Small/Large changes almost nothing (the thread reader, compose, settings, calendar, contacts all stay fixed).
|
||||
- Fix hint: either make `tokens.typography` a hook-backed getter or, cheaper, set `Text.defaultProps`/`maxFontSizeMultiplier` and scale via `allowFontScaling` + a root `PixelRatio` factor.
|
||||
|
||||
- [x] **`animationsEnabled` ignored by most animations; no reduce-motion respect** — fixed in 3ae9bbe — `P3` — `partial` — useShouldAnimate ORs in AccessibilityInfo.isReduceMotionEnabled; the 18 fixed-duration Animated.timing calls in other areas' files still need useAnimDuration
|
||||
@@ -250,7 +250,7 @@ RN toggles that are stored but never read (fix: either wire them or remove the c
|
||||
- What RN does: `RN: app.config.js:33` sets `userInterfaceStyle: 'dark'`, which prebuild writes as `UIUserInterfaceStyle = Dark` in Info.plist, so `useColorScheme()` (`src/theme/colors.ts:16`, `App.tsx:220`) always returns `dark` and "System" behaves like "Dark". On Android without `expo-system-ui` (not in `package.json`) the key is ignored with a prebuild warning, so Android is probably fine. Splash background is also dark-only (`:38`).
|
||||
- Fix hint: set `userInterfaceStyle: 'automatic'` and give the splash a light variant (`splash.dark`).
|
||||
|
||||
- [x] **Themes tab is a stub with fake themes; `activeThemeId` never applied** — fixed in fe45a44 — `P3` — `missing` (record as N/A for now) — Qui/Nord/Catppuccin/Solarized token sets generated from lib/builtin-themes.ts (src/theme/builtin-themes.ts); useColors() applies activeThemeId; upload button removed
|
||||
- [x] **Themes tab is a stub with fake themes; `activeThemeId` never applied** — fixed in fe45a44 — `P3` — `missing` (record as N/A for now) — Qui/Nord/Catppuccin/Solarized token sets generated from lib/builtin-themes.ts (src/theme/builtin-themes.ts); useColors() applies activeThemeId; upload button removed; Roundcube Elastic and Aurora Glass added in 7964e64, "Flat fields" not ported
|
||||
- What WEB does: 6 built-in themes (`lib/builtin-themes.ts:879-936`), zip upload, marketplace, admin forced/default theme, compiled CSS tokens, PWA theme-color meta (`stores/theme-store.ts`).
|
||||
- What RN does: `ThemesSettings.tsx:18-22` lists `Default/Qui/Sepia` (Qui exists in WEB; Sepia does not), stores `activeThemeId` that nothing reads, "Upload .zip" has no handler (`:81-83`); the tab is marked Experimental.
|
||||
- Fix hint: minimal viable subset = map WEB's built-in theme token sets (`builtin-themes.ts` light/dark `colors`) onto `ThemePalette` and let `useColors()` pick `activeThemeId`; remove the upload button. Otherwise hide the tab.
|
||||
@@ -294,7 +294,7 @@ RN toggles that are stored but never read (fix: either wire them or remove the c
|
||||
|
||||
### Misc UI
|
||||
|
||||
- [x] **Deep-link scheme registered but nothing handles it** — fixed in 5802cae — `P2` — `missing` — bulwarkmobile://, webmail https permalinks, mailto: (scheme + SENDTO filter) and ACTION_SEND/SEND_MULTIPLE share targets → Compose; iOS universal links still deferred
|
||||
- [x] **Deep-link scheme registered but nothing handles it** — fixed in 5802cae — `P2` — `missing` — bulwarkmobile://, webmail https permalinks, mailto: (scheme + SENDTO filter) and ACTION_SEND/SEND_MULTIPLE share targets → Compose; iOS universal links still deferred; shared files never uploaded, `SENDTO` opened the inbox and `+` addresses opened nothing until cd4ce9d, 06e8942 and ee652d3 (audit B9–B11)
|
||||
- What WEB does: permalinks for mail/calendar/contacts/files/settings with build/parse helpers (`lib/deep-links.ts:196-235,272-311,322-370,379-414`), `mailto:` opens the built-in composer, protocol handler registration, `webcal:`.
|
||||
- What RN does: `scheme: 'bulwarkmobile'` (`RN: app.config.js:29`) and a `VIEW`/`BROWSABLE` intent filter (`AndroidManifest.xml:25-30`) exist, but `NavigationContainer` has no `linking` prop (`App.tsx:443`) and there is no `Linking.getInitialURL`/`addEventListener('url')` anywhere, so tapping a `bulwarkmobile://` URL merely launches the app. No `https` App Links for the webmail permalink format, no `mailto:` intent filter (tapping a mailto link in another app never offers Bulwark), no `SEND`/`SEND_MULTIPLE` share target.
|
||||
- Fix hint: add a `linking` config mapping `bulwarkmobile://mail/message/:emailId`, `/calendar/event/:id`, `/contacts/:id`, `/settings/:tab` to the existing routes (`src/navigation/types.ts`), reuse WEB's path grammar so a webmail permalink can be rewritten; add `<data android:scheme="mailto"/>` and `ACTION_SEND` (`text/*`, `image/*`) filters routed to `Compose` with `prefillTo`/attachments. iOS: `CFBundleURLTypes` via `scheme` is already emitted; add `associatedDomains` for universal links later.
|
||||
@@ -314,7 +314,7 @@ RN toggles that are stored but never read (fix: either wire them or remove the c
|
||||
- What RN does: `OfflineBanner.tsx:22-29` hard-codes "You are offline" with an English plural; network-store logic itself is sound (`network-store.ts:16-22` treats `isInternetReachable === null` as online) and App.tsx retries the session on reconnect (`:245-252`).
|
||||
- Fix hint: `t('common.offline', ...)` and reuse plural helper once interpolation exists.
|
||||
|
||||
- [ ] **Accessibility labels almost absent** — `P3` — `partial` — done in my files (RadioGroup/Select roles, Dialog header, Settings back button, ToastHost, UpdateBanner, AccountSettings, security key rows); other areas' screens untouched
|
||||
- [x] **Accessibility labels almost absent** — `P3` — `partial` — done in my files (RadioGroup/Select roles, Dialog header, Settings back button, ToastHost, UpdateBanner, AccountSettings, security key rows); the mail list, composer, Files, contacts, sign-in and settings switches followed in 9c0c35c, 771558d, aab29da, d59c571, 5801d6c, cb5c152 and c1e5cd6, and the viewer, calendar, tab bar, unified inbox and drawer in 13cba64, b3a82f1, 84fef14 and b178ee8 (audit U8)
|
||||
- What WEB does: aria-labels on icon buttons, `role="checkbox"`, screen-reader improvements (changelog 1.8.0 Navigation).
|
||||
- What RN does: `accessibilityLabel`/`Role` appear in 8 of 94 `.tsx` files (FilesScreen 4, EmailListScreen 3, login 4, EmailThreadScreen 2, ToggleSwitch 1). Icon-only `Pressable`s in settings (`SettingsScreen.tsx:217-222` back button, `SidebarAppsSettings.tsx:91-96`, `SmimeSettings.tsx:91-106`, `UpdateBanner.tsx:40`), the tab bar badge and the `RadioGroup`/`Select` in `settings-section.tsx` have none.
|
||||
- Fix hint: give every icon-only Pressable an `accessibilityLabel` + `accessibilityRole="button"`, `ToggleSwitch` `accessibilityRole="switch"` + `accessibilityState`, `RadioGroup` options `radio`.
|
||||
|
||||
@@ -52,14 +52,14 @@ RN's JMAP client (`src/api/jmap-client.ts`, 615 lines) is a thin transport: sess
|
||||
- What RN does: `rewriteSessionUrls` only rewrites when `extractOrigin(url)` matches `^https?://` (`src/api/jmap-client.ts:320-340`); a relative `apiUrl` is returned unchanged and `fetch('/jmap/')` then fails with "Network request failed". Same gap in `src/api/unified-inbox.ts:55-60`. (RN correctly avoids `new URL()` so templates are not corrupted.)
|
||||
- Fix hint: in `rewrite()`, when `extractOrigin` is null and the string starts with `/`, return `serverOrigin + url`.
|
||||
|
||||
- [x] **Capability check uses the broad `principals` URN, not `principals:owner` (44360024)** — `P2` — `bugfix-parity` — fixed in a0ce925
|
||||
- [x] **Capability check uses the broad `principals` URN, not `principals:owner` (44360024)** — `P2` — `bugfix-parity` — fixed in a0ce925; since Stalwart advertises `principals:owner` nowhere, that also hid sharing, so since caea3d0 the sharing UI is gated on `principals` and `principals:owner` only goes into `using` when advertised
|
||||
- What WEB does: declares `urn:ietf:params:jmap:principals:owner` in `using` only when that exact capability is advertised in session or account capabilities (`lib/jmap/client.ts:4689-4698`, `6472`; changelog "Check the specific capability a request declares (principals:owner), not a broader one").
|
||||
- What RN does: `supportsSharing()` checks `CAPABILITIES.PRINCIPALS` (`src/api/files.ts:23-25`) and then pushes `PRINCIPALS_OWNER` into `using` (`files.ts:40-43`). A server advertising `principals` but not `principals:owner` rejects every FileNode request with `unknownCapability`.
|
||||
- Fix hint: check `hasCapability(CAPABILITIES.PRINCIPALS_OWNER) || accountCapabilities[...]` before adding it.
|
||||
|
||||
- [x] **PatchObject pointer keys not JSON-Pointer escaped (5c484af1)** — `P3` — `bugfix-parity` — fixed in ccbe67c
|
||||
- What WEB does: `keywordPointer()`/`pointerToken()` escape `~` and `/` (`lib/jmap/patch-pointer.ts:39-55`); mailbox moves use a whole-map replacement (`lib/jmap/client.ts:66-74`).
|
||||
- What RN does: `moveEmail`/`moveEmails` build `mailboxIds/${id}` pointers unescaped (`src/api/email.ts:456-457`, `473-474`). Stalwart ids never contain `/`, so exposure is limited to non-Stalwart servers; keywords are always sent as a whole map (`email.ts:436-445`), so tag names with `/` are safe.
|
||||
- What RN does: `moveEmail`/`moveEmails` build `mailboxIds/${id}` pointers unescaped (`src/api/email.ts:456-457`, `473-474`). Stalwart ids never contain `/`, so exposure is limited to non-Stalwart servers; keywords are always sent as a whole map (`email.ts:436-445`), so tag names with `/` are safe. (Since 5041897 keywords are sent as `keywords/<name>` pointers too, escaped by `keywordPointer` in `src/api/patch-pointer.ts`.)
|
||||
- Fix hint: add `pointerToken()` or switch `moveEmail(s)` to the full `mailboxIds` replacement RN already uses in `restoreEmailMailboxes`.
|
||||
|
||||
- [x] **Send: message created directly in Sent instead of Drafts + `onSuccessUpdateEmail` (#188)** — `P2` — `bugfix-parity` — fixed in ccbe67c
|
||||
@@ -96,7 +96,7 @@ RN's JMAP client (`src/api/jmap-client.ts`, 615 lines) is a thin transport: sess
|
||||
- What RN does: `fetchMailboxes` does one `Mailbox/get`; an empty list on a brand-new Stalwart account stays empty until pull-to-refresh (`src/stores/email-store.ts:584-588`).
|
||||
- Fix hint: if the list is empty and there is no `inbox` role, retry after ~1 s up to 3 times.
|
||||
|
||||
- [x] **Session redirect with dropped Authorization not handled** — `P3` — `partial` — fixed in 0b1c240
|
||||
- [x] **Session redirect with dropped Authorization not handled** — `P3` — `partial` — fixed in 0b1c240; the check keyed on `response.redirected`, which React Native's fetch never sets, so it only works since 859770b (audit B15)
|
||||
- What WEB does: detects `response.redirected` + empty accounts/username and refetches the final URL with the header (`lib/jmap/client.ts:911-925`).
|
||||
- What RN does: `fetchSession` accepts whatever comes back; `resolveAccountId` then throws "No account found in JMAP session" (`src/api/jmap-client.ts:363-401`). iOS strips Authorization on cross-origin redirects.
|
||||
- Fix hint: same detection; retry against `response.url`.
|
||||
@@ -128,7 +128,7 @@ RN's JMAP client (`src/api/jmap-client.ts`, 615 lines) is a thin transport: sess
|
||||
- What RN does: `Email`/`EmailDelivery`/`Mailbox` (`src/stores/email-store.ts:981-1020`), `AddressBook`/`ContactCard`/`Contact` (`src/stores/contacts-store.ts:152-168`), `Calendar`/`CalendarEvent` incl. shared accounts (`src/stores/calendar-store.ts:261-283`). `ScheduledScreen`, `filter-store`, `vacation-store`, Files and Identity never refresh on push (`grep handleStateChange src/stores/filter-store.ts src/stores/vacation-store.ts` empty).
|
||||
- Fix hint: add `EmailSubmission` -> scheduled list refresh (when the Scheduled screen is mounted), `SieveScript` -> `filter-store.fetch`, `VacationResponse` -> vacation store, `FileNode` -> Files screen refresh; dispatch from the `onStateChange` fan-out in `App.tsx:391-396`.
|
||||
|
||||
- [ ] **Contacts state changes ignore shared/group accounts** — `P3` — `partial`
|
||||
- [x] **Contacts state changes ignore shared/group accounts** — `P3` — `partial` — done in 26a34d5 (`handleStateChange` walks `getContactCapableAccountIds()`)
|
||||
- What WEB does: per-account clients; `handleStateChange` reacts to the client's own account (`stores/email-store.ts:3255-3257`); calendar/contacts stores use the account of the shared collection.
|
||||
- What RN does: `contacts-store.handleStateChange` only reads `change.changed[jmapClient.accountId]` (`src/stores/contacts-store.ts:154-156`) although address books from shared accounts are shown; `calendar-store` already folds shared accounts in (`calendar-store.ts:263-272`).
|
||||
- Fix hint: mirror the calendar store's `known` set using `addressBook.accountId`.
|
||||
@@ -143,7 +143,7 @@ RN's JMAP client (`src/api/jmap-client.ts`, 615 lines) is a thin transport: sess
|
||||
- What RN does: `react-native-sse` accumulates `xhr.responseText` for the life of the connection (`EventSource.js:108`, `_lastIndexProcessed`), and RN requests `{ping}=30`, `{closeafter}=no` (`src/api/push.ts:126-129`), so memory grows for every ping/event until reconnect. No ping-timeout detection either.
|
||||
- Fix hint: either request `closeafter=state` (server closes after each event; library auto-reconnects) or recycle the EventSource every ~30 min / when no `ping` event arrived in 90 s (add a `ping` listener).
|
||||
|
||||
- [x] **Push subscription `types` include `Email` and `Mailbox`; no `emailPush` spam filter** — `P2` — `partial` — fixed in 00faceb
|
||||
- [x] **Push subscription `types` include `Email` and `Mailbox`; no `emailPush` spam filter** — `P2` — `partial` — fixed in 00faceb; the `emailPush` map was refused for ACL-shared mailboxes and never read back until d8046a8 and ccaa24e (audit B18)
|
||||
- What WEB does: subscribes to `EmailDelivery` only, plus an `emailPush` filter excluding `$junk`/Junk when the server advertises `urn:ietf:params:jmap:emailpush` (`lib/web-push.ts:39-52`, `lib/jmap/client.ts:8085-8142`; changelog 1.9.x "Stop sending notifications for spam").
|
||||
- What RN does: `PUSH_TYPES = ['Email', 'EmailDelivery', 'Mailbox']` (`src/lib/push-notifications.ts:104`); `createPushSubscription` has no `emailPush` support (`src/api/push.ts:23-57`). Every read/flag/move on any device wakes the relay -> FCM -> headless task, which then re-queries the inbox (`src/lib/push-background-task.ts:452-455`); a spam delivery still triggers a push (only the inbox `notKeyword $seen` query hides it, at the cost of a wake-up).
|
||||
- Fix hint: subscribe to `EmailDelivery` only; add `hasEmailPushCapability()` + `emailPush` filter as in WEB; the relay must forward it (see memory note: relay lacks EmailPush).
|
||||
@@ -181,7 +181,7 @@ RN's JMAP client (`src/api/jmap-client.ts`, 615 lines) is a thin transport: sess
|
||||
- What RN does: `android:allowBackup="true"` with `@xml/secure_store_backup_rules` from the expo-secure-store plugin, which only excludes SecureStore's own prefs (`android/app/src/main/AndroidManifest.xml:16`); the AsyncStorage SQLite DB (full cached bodies `webmail:offline-cache:entry:v2:*`, outbox, settings, account registry) is backed up to the user's Google account.
|
||||
- Fix hint: add an app-owned `fullBackupContent`/`dataExtractionRules` excluding `databases/RKStorage` (AsyncStorage) or set `allowBackup=false` via an Expo config plugin.
|
||||
|
||||
- [ ] **Send has no offline path and no draft fallback** — `P2` — `missing`
|
||||
- [ ] **Send has no offline path and no draft fallback** — `P2` — `missing` — partly: drafts autosave to the server since 5b72c4d, and since 92b42e4 the close dialog no longer drops the text when the draft can't be saved; an offline send is still not queued, and a draft can't be saved offline
|
||||
- What WEB does: no offline queue either, but send failures leave the composer open with the draft autosaved (composer agent's area); send itself has a deadline (#702).
|
||||
- What RN does: `ComposeScreen` shows `Alert('Send failed')` and keeps the modal (`src/screens/ComposeScreen.tsx:975-979`); there is no draft API in `src/api/email.ts` (`grep -n '[Dd]raft' src/api/email.ts` only finds the send create id), so the body is lost if the user leaves, and an offline send is not queued although the outbox exists.
|
||||
- Fix hint: either add an `outbox` `send` op kind (blob uploads must already be done) or at minimum persist the composer state locally on failure.
|
||||
@@ -298,11 +298,11 @@ RN's JMAP client (`src/api/jmap-client.ts`, 615 lines) is a thin transport: sess
|
||||
- `Email/queryChanges` + `Email/changes` incremental list refresh with snapshot guard (issue #10) and full re-query fallback (`src/stores/email-store.ts:770-980`).
|
||||
- `null` for patch removal, `false` stripped from `mailboxIds` maps (`src/api/email.ts:456`, `551-556`).
|
||||
- FilterOperator/FilterCondition kept separate in query filters (`src/api/email.ts:225-237`).
|
||||
- `getSharedMailboxes` chunks to `maxCallsInRequest` (`src/api/email.ts:84-105`); `getEvents`/`getContacts`/`fetchEmailsChunked`/offline sync chunk to `maxObjectsInGet`.
|
||||
- Send: `/error`, `notCreated` checked; HOLDFOR envelope for scheduled send; FUTURERELEASE capability check (`src/api/email.ts:836-873`, `src/api/jmap-client.ts:505-531`).
|
||||
- Push subscription create/verify/update/destroy with `using: [core]` and 90-day expiry refresh (`src/api/push.ts:9-114`, `src/lib/push-notifications.ts`).
|
||||
- `getSharedMailboxes` chunks to `maxCallsInRequest` (`src/api/email.ts:84-105`); `getEvents`/`getContacts`/offline sync chunk to `maxObjectsInGet`, and list pages are capped at it since `queryEmailPage` replaced `fetchEmailsChunked` (d987930).
|
||||
- Send: `/error`, `notCreated` checked; HOLDFOR envelope for scheduled send; FUTURERELEASE capability check (`src/api/email.ts:836-873`, `src/api/jmap-client.ts:505-531`). Two gaps found later: the capability was read from the session instead of the account until e28e8b4 (#57, audit B8), and a method error after a created submission (Stalwart's failed filing into Sent) was reported as a failed send until c51a848 (audit B23).
|
||||
- Push subscription create/verify/update/destroy with `using: [core]` and 90-day expiry refresh (`src/api/push.ts:9-114`, `src/lib/push-notifications.ts`); calls that read or write `emailPush` also name `urn:ietf:params:jmap:emailpush` since ccaa24e, and every call checks the response since ddfe85a.
|
||||
- Outbox: idempotent full-state ops, per-account buckets, destroy-wins coalescing, oldest-first replay, account-switch guards (`src/stores/outbox-store.ts`).
|
||||
- Offline cache: per-account buckets, index + entry keys, eviction by `receivedAt`, optimistic `patch`, abort on account switch (`src/stores/offline-cache-store.ts`); `getEmailDetail` cache fallback.
|
||||
- Offline cache: per-account buckets, index + entry keys, eviction by `receivedAt`, optimistic `patch`, abort on account switch (`src/stores/offline-cache-store.ts`); the offline copy is shown before the network since 33e303e (`src/lib/email-detail-cache`, which replaced `getEmailDetail`).
|
||||
- Credentials in `expo-secure-store` (never AsyncStorage); SecureStore key sanitising; per-account keys; legacy migration.
|
||||
- Client-cert module uses the platform default `X509TrustManager` (no trust-all) (`BulwarkClientCertModule.kt:216-222`).
|
||||
- WebView hardening: `originWhitelist=['about:blank']`, no file/universal access, `mixedContentMode="never"`, no multiple windows, no cookies/DOM storage, `incognito`, external links limited to `https?|mailto|tel|sms` (`src/components/EmailBodyView.tsx:675-724`).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createElement } from 'react';
|
||||
import { AppRegistry } from 'react-native';
|
||||
import { registerRootComponent } from 'expo';
|
||||
|
||||
import App from './App';
|
||||
import { pushBackgroundTask } from './src/lib/push-background-task';
|
||||
|
||||
// Runs in a fresh headless JS runtime when BulwarkPushTaskService is started
|
||||
@@ -9,7 +9,15 @@ import { pushBackgroundTask } from './src/lib/push-background-task';
|
||||
// before the native service tries to invoke the task.
|
||||
AppRegistry.registerHeadlessTask('BulwarkPushTask', () => pushBackgroundTask);
|
||||
|
||||
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
|
||||
// A headless push task evaluates this file too, so App is required when the
|
||||
// UI first renders instead of imported above: importing it here would load
|
||||
// every screen, store and icon before the task could run.
|
||||
function Root() {
|
||||
const App = (require('./App') as typeof import('./App')).default;
|
||||
return createElement(App);
|
||||
}
|
||||
|
||||
// registerRootComponent calls AppRegistry.registerComponent('main', () => Root);
|
||||
// It also ensures that whether you load the app in Expo Go or in a native build,
|
||||
// the environment is set up appropriately
|
||||
registerRootComponent(App);
|
||||
registerRootComponent(Root);
|
||||
|
||||
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "و{count} آخرين",
|
||||
"draft": "مسودة",
|
||||
"starred": "مميزة بنجمة",
|
||||
"pinned": "مثبتة",
|
||||
"replied": "تم الرد عليها",
|
||||
"forwarded": "تمت إعادة توجيهها",
|
||||
"has_attachment": "تحتوي على مرفق",
|
||||
"conversations_count": "{count} من {total} محادثة",
|
||||
"conversations_count_plus": "{count}+ محادثة",
|
||||
"conversations_count_simple": "{count} محادثة",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "إعادة توجيه",
|
||||
"delete": "حذف",
|
||||
"archive": "أرشفة",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "تمييز بنجمة",
|
||||
"unstar": "إزالة النجمة",
|
||||
"mark_unread": "تمييز كغير مقروءة",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "نقل",
|
||||
"print": "طباعة",
|
||||
"view_source": "عرض المصدر",
|
||||
|
||||
"fullscreen": "ملء الشاشة",
|
||||
|
||||
"exit_fullscreen": "الخروج من ملء الشاشة",
|
||||
"export_email": "تصدير كملف .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "إعادة التوجيه كمرفق",
|
||||
"import_email": "استيراد ملف .eml أو .zip",
|
||||
"keyboard_shortcuts": "اختصارات لوحة المفاتيح (؟)",
|
||||
"email_source": "مصدر الرسالة",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "إيصال القراءة مطلوب (انقر للتعطيل)",
|
||||
"read_receipt_off": "طلب إيصال قراءة",
|
||||
"dsn_on": "تم طلب إشعار التسليم (انقر للتعطيل)",
|
||||
"dsn_off": "طلب إشعار بالتسليم",
|
||||
"format_plain_text": "التبديل إلى نص عادي",
|
||||
"format_rich_text": "التبديل إلى نص منسق (HTML)",
|
||||
"require_tls_on": "التسليم المشفّر مطلوب (انقر للتعطيل)",
|
||||
"require_tls_off": "اشتراط التسليم المشفّر (TLS)",
|
||||
"new_message": "رسالة جديدة",
|
||||
"signature_edit_hint": "انقر نقرًا مزدوجًا لتحرير التوقيع",
|
||||
"reply": "رد",
|
||||
@@ -634,11 +643,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "تجاوز",
|
||||
"toggle_on": "إلغاء التجاوز",
|
||||
"toggle_tooltip": "عدّل اسم وعنوان \"من\" بحرية. سيُرسل البريد عبر هويتك، لكن ترويسة \"من\" الظاهرة فقط هي التي تتغير.",
|
||||
"toggle_tooltip": "عدّل اسم وعنوان \"من\" بحرية. سيُرسل البريد عبر هويتك.",
|
||||
"name_label": "اسم المرسل",
|
||||
"name_placeholder": "الاسم",
|
||||
"email_label": "عنوان بريد المرسل",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "إذا لم يقبل خادمك هذا العنوان كمرسل للمغلف، فسيُستخدم {identity} بدلاً منه، ويمكن للمستلمين رؤيته في ترويسة Return-Path."
|
||||
},
|
||||
"use_template": "قالب",
|
||||
"save_as_template": "حفظ كقالب",
|
||||
@@ -651,6 +661,8 @@
|
||||
"upload_progress": "جارٍ الرفع {uploaded} / {total}",
|
||||
"upload_cancel": "إلغاء الرفع",
|
||||
"upload_failed": "فشل رفع {filename}",
|
||||
"attachment_too_large": "\"{name}\" أكبر مما يسمح به الخادم ({max} لكل ملف)",
|
||||
"attachments_total_too_large": "ستتجاوز المرفقات حد الخادم البالغ {max} لكل رسالة",
|
||||
"drop_files": "أفلت الملفات لإرفاقها",
|
||||
"show_less": "إظهار أقل",
|
||||
"send_failed": "فشل إرسال الرسالة",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "حذف العمود",
|
||||
"toggle_header_row": "تبديل صف الرأس",
|
||||
"delete_table": "حذف الجدول",
|
||||
"pick_size": "اختيار الحجم"
|
||||
"pick_size": "اختيار الحجم",
|
||||
"font_size": "حجم الخط",
|
||||
"font_size_default": "افتراضي",
|
||||
"background_color": "لون الخلفية",
|
||||
"remove_background_color": "إزالة لون الخلفية"
|
||||
},
|
||||
"send_filing_warning": "تم الإرسال - لكن التنظيف بعد الإرسال فشل، وقد تبقى مسودة قديمة."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "تم إرسال الرسالة بنجاح",
|
||||
"email_deleted": "تم حذف الرسالة",
|
||||
"email_archived": "تمت أرشفة الرسالة",
|
||||
"emails_deleted": "{count, plural, one {تم حذف رسالة واحدة} other {تم حذف # رسائل}}",
|
||||
"emails_archived": "{count, plural, one {تمت أرشفة رسالة واحدة} other {تمت أرشفة # رسائل}}",
|
||||
"error_archiving": "فشل أرشفة الرسالة",
|
||||
"error_updating": "فشل تحديث الرسالة",
|
||||
"email_starred": "تم تمييز الرسالة بنجمة",
|
||||
"email_unstarred": "تمت إزالة النجمة عن الرسالة",
|
||||
"email_marked_read": "تم تمييز الرسالة كمقروءة",
|
||||
@@ -1144,6 +1164,7 @@
|
||||
"status_active": "نشط على هذا الجهاز",
|
||||
"status_inactive": "غير مفعّل على هذا الجهاز",
|
||||
"status_unsupported": "هذا المتصفح لا يدعم Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"status_busy": "جارٍ العمل…",
|
||||
"enable": "تفعيل",
|
||||
"reenable": "إعادة التسجيل",
|
||||
@@ -1181,7 +1202,9 @@
|
||||
"enabled": "إشعارات البريد",
|
||||
"enabled_desc": "إظهار الإشعارات عند وصول رسائل جديدة",
|
||||
"sound": "صوت الإشعار",
|
||||
"sound_desc": "تشغيل تنبيه صوتي عند وصول رسائل جديدة"
|
||||
"sound_desc": "تشغيل تنبيه صوتي عند وصول رسائل جديدة",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "إشعارات التقويم",
|
||||
@@ -1241,6 +1264,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "مسح البحث عند تبديل المجلدات",
|
||||
"description": "النقر على مجلد (البريد الوارد، المرسل…) يمسح البحث النشط ويعرض ذلك المجلد. عند الإيقاف، يبقى البحث ويُعاد تشغيله في المجلد الذي تنتقل إليه."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "طلب إيصالات القراءة افتراضيًا",
|
||||
"description": "تفعيل طلب إيصال القراءة مسبقًا عند إنشاء رسالة جديدة."
|
||||
@@ -1324,6 +1351,12 @@
|
||||
"label": "الرد من العنوان المستلَم إليه",
|
||||
"description": "عند الرد، الإرسال من العنوان الذي أُرسلت إليه الرسالة أصلًا. يطابق الهويات أولًا؛ وبالنسبة لعمليات التسليم الشاملة للنطاق، يعيد كتابة ترويسة \"من\" إلى العنوان البديل مع الإرسال عبر هويتك الأساسية."
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "مطابقة عنوان الاستلام",
|
||||
"description": "أي عناوين الاستلام تُعدّ عناوينك. «العنوان المطابق فقط» يختار إحدى هوياتك المضبوطة فقط؛ أما «نفس النطاق» فيعامل أيضًا أي عنوان آخر على نطاقات هوياتك كاسم مستعار شامل ويستبدل به ترويسة المرسل. اختر العنوان المطابق إذا كانت تلك العناوين قوائم توزيع.",
|
||||
"exact": "العنوان المطابق فقط",
|
||||
"domain": "أي عنوان على نطاقاتي"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "موضع التوقيع",
|
||||
"description": "أين يُدرج توقيعك في الردود وإعادة التوجيه. أعلى النص المقتبس يُقرأ بشكل طبيعي كخاتمة للرد؛ وأسفله يُبقي الرسالة الأصلية متصلة.",
|
||||
@@ -1753,7 +1786,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "يجب أن يكون تاريخ الانتهاء بعد تاريخ البدء",
|
||||
"start_in_past": "تاريخ البدء في الماضي",
|
||||
"empty_body": "نص الرسالة فارغ - سيتلقى المستلمون ردًا فارغًا"
|
||||
"empty_body": "نص الرسالة فارغ - سيتلقى المستلمون ردًا فارغًا",
|
||||
"subject_too_long": "الموضوع طويل جدًا بالنسبة للخادم (بحد أقصى {max} بايت).",
|
||||
"body_too_long": "الرسالة طويلة جدًا بالنسبة للخادم (بحد أقصى {max} بايت بما في ذلك التنسيق)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1770,6 +1805,8 @@
|
||||
"role_trash": "المهملات",
|
||||
"role_junk": "البريد المزعج",
|
||||
"role_archive": "الأرشيف",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "بلا",
|
||||
"create_folder": "إنشاء مجلد",
|
||||
"create_subfolder": "إنشاء مجلد فرعي",
|
||||
@@ -1879,6 +1916,8 @@
|
||||
"title": "جهات الاتصال",
|
||||
"description": "استيراد وتصدير جهات اتصالك",
|
||||
"group_by_letter_label": "التجميع حسب الحرف الأول",
|
||||
"sort_by_last_name_label": "الترتيب حسب اسم العائلة",
|
||||
"sort_by_last_name_description": "ترتيب قائمة جهات الاتصال حسب اسم العائلة بحيث يظهر أفراد العائلة معًا",
|
||||
"group_by_letter_description": "إظهار عناوين أقسام أبجدية في قائمة جهات الاتصال",
|
||||
"import_label": "استيراد جهات الاتصال",
|
||||
"import_description": "استيراد جهات الاتصال من ملف vCard (.vcf)",
|
||||
@@ -1970,6 +2009,7 @@
|
||||
"actions": "الإجراءات",
|
||||
"add_action": "إضافة إجراء",
|
||||
"stop_processing": "إيقاف معالجة القواعد التالية",
|
||||
"include_spam": "نقل الرسائل المصنفة كرسائل مزعجة أيضًا",
|
||||
"attachment_type_placeholder": "مثال: pdf, doc, jpg",
|
||||
"value_placeholder_multi": "القيمة (عدة قيم مفصولة بفواصل)",
|
||||
"condition_fields": {
|
||||
@@ -2011,6 +2051,8 @@
|
||||
"copy_to_folder": "اختر المجلد",
|
||||
"forward_to": "إعادة التوجيه إلى عنوان بريد إلكتروني",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "الاحتفاظ بنسخة",
|
||||
"forward_limit": "حد إعادة التوجيه لكل رسالة على هذا الخادم: {count}. يتم تخطي عمليات إعادة التوجيه الإضافية.",
|
||||
"reject_message": "رسالة الرفض",
|
||||
"reject_placeholder": "تم رفض رسالتك",
|
||||
"label_name": "اسم الوسم",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "مجلد فرعي جديد...",
|
||||
"new_folder": "مجلد جديد...",
|
||||
"rename": "إعادة تسمية...",
|
||||
"share": "مشاركة...",
|
||||
"import_email": "استيراد ملف .eml أو .zip...",
|
||||
"empty_folder": "إفراغ المجلد",
|
||||
"empty_folder_generic": "إفراغ المجلد",
|
||||
@@ -2236,7 +2279,10 @@
|
||||
"toast_error_rename": "فشلت إعادة تسمية المجلد",
|
||||
"toast_error_delete": "فشل حذف المجلد",
|
||||
"toast_error_delete_has_children": "المجلد يحتوي على مجلدات فرعية. أزلها أولًا.",
|
||||
"toast_error_delete_has_email": "المجلد ليس فارغًا. أفرغه أولًا."
|
||||
"toast_error_delete_has_email": "المجلد ليس فارغًا. أفرغه أولًا.",
|
||||
"delete_with_contents_title": "المجلد ليس فارغًا",
|
||||
"delete_with_contents_message": "لا يزال المجلد \"{name}\" يحتوي على {count, plural, one {رسالة واحدة} other {# رسائل}}. هل تريد حذف المجلد وكل رسائله؟ لا يمكن التراجع عن هذا.",
|
||||
"delete_with_contents_confirm": "حذف المجلد والرسائل"
|
||||
},
|
||||
"shortcuts": {
|
||||
"title": "اختصارات لوحة المفاتيح",
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "التركيز على البحث",
|
||||
"help": "إظهار الاختصارات",
|
||||
"refresh": "تحديث الرسائل",
|
||||
"select_all": "تحديد الكل"
|
||||
"select_all": "تحديد الكل",
|
||||
"global_search": "البحث في كل شيء"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "توسيع/طي المحادثة"
|
||||
@@ -2722,7 +2769,10 @@
|
||||
"any_month": "أي شهر",
|
||||
"has_email": "لديه بريد إلكتروني",
|
||||
"has_phone": "لديه هاتف",
|
||||
"has_photo": "لديه صورة"
|
||||
"has_photo": "لديه صورة",
|
||||
"sort_by": "الترتيب حسب",
|
||||
"sort_first_name": "الاسم الأول",
|
||||
"sort_last_name": "اسم العائلة"
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "تكرار",
|
||||
"today_header": "اليوم",
|
||||
"tomorrow_header": "غدًا",
|
||||
"agenda_show_earlier": "عرض الأحداث السابقة",
|
||||
"agenda_loading": "جارٍ التحميل…",
|
||||
"agenda_range_start": "الأحداث من {date}",
|
||||
"agenda_range_end": "الأحداث حتى {date}",
|
||||
"export_ics": "التصدير كملف .ics",
|
||||
"copy_title": "نسخ العنوان",
|
||||
"copy_link": "نسخ رابط الاجتماع",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "اللون"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "التوفر",
|
||||
"free": "متاح",
|
||||
"busy": "مشغول",
|
||||
"tentative": "مبدئي",
|
||||
"unknown": "ليس مستخدمًا على هذا الخادم",
|
||||
"checking": "جارٍ التحقق…"
|
||||
},
|
||||
"title": "المشاركون",
|
||||
"add": "إضافة مشارك",
|
||||
"organizer": "المنظّم",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "جميع الأحداث",
|
||||
"cancel": "إلغاء",
|
||||
"save": "حفظ",
|
||||
"delete": "حذف"
|
||||
"delete": "حذف",
|
||||
"rsvp_title": "الرد على حدث متكرر",
|
||||
"rsvp_description": "هذا حدث متكرر. على أي الأحداث ينطبق ردك؟",
|
||||
"respond": "رد"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "التذكيرات",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "عرض أوقات الأحداث في عرض التقويم الشهري",
|
||||
"show_week_numbers": "إظهار أرقام الأسابيع",
|
||||
"show_week_numbers_desc": "عرض أرقام الأسابيع في التقويم المصغّر",
|
||||
"calendar_free_scroll": "تمرير حر",
|
||||
"calendar_free_scroll_desc": "التمرير باستمرار عبر الأشهر والأسابيع والأيام بدلاً من فترة واحدة في كل مرة",
|
||||
"enable_tasks": "تفعيل المهام",
|
||||
"enable_tasks_desc": "إظهار عرض للمهام في التقويم لإدارة قوائم المهام",
|
||||
"show_tasks_on_calendar": "إظهار المهام في التقويم",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "تأخير ثانية واحدة",
|
||||
"hover_preview_delay_2s": "تأخير ثانيتين",
|
||||
"hover_preview_off": "معطّل",
|
||||
"organizer_identity": "تنظيم الدعوات باسم",
|
||||
"organizer_identity_desc": "عنوان التقويم المستخدم كمنظّم عند دعوة الآخرين إلى الأحداث.",
|
||||
"organizer_identity_failed": "فشل تغيير عنوان المنظّم",
|
||||
"show_birthday_calendar": "تقويم أعياد ميلاد جهات الاتصال",
|
||||
"show_birthday_calendar_desc": "إظهار تقويم افتراضي بأعياد ميلاد جهات اتصالك"
|
||||
},
|
||||
@@ -2973,6 +3043,9 @@
|
||||
"alert_now": "يبدأ الآن",
|
||||
"alert_in_minutes": "خلال {count} دقيقة",
|
||||
"invitation_sent": "تم إرسال الدعوات",
|
||||
"invitations_denied_title": "تعذر إرسال الدعوات",
|
||||
"invitations_denied": "رفض الخادم إرسال الدعوات: {reason}. هل تريد حفظ الحدث دون إرسالها؟",
|
||||
"save_without_invitations": "حفظ دون دعوات",
|
||||
"rsvp_updated": "تم تحديث الرد",
|
||||
"rsvp_error": "فشل تحديث الرد",
|
||||
"event_duplicated": "تم تكرار الحدث",
|
||||
@@ -3002,7 +3075,7 @@
|
||||
"supported_formats": "يدعم ملفات iCalendar (.ics)",
|
||||
"url_description": "أدخل رابط تغذية iCalendar (.ics) خارجية لاستيراد الأحداث.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "يدعم روابط CalDAV وiCalendar (.ics)",
|
||||
"url_hint": "يدعم روابط iCalendar (.ics)",
|
||||
"fetch": "جلب",
|
||||
"invalid_url": "يرجى إدخال رابط صالح",
|
||||
"url_fetch_failed": "فشل جلب التقويم من الرابط",
|
||||
@@ -3149,6 +3222,25 @@
|
||||
"overdue": "متأخرة"
|
||||
}
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "شخص ما",
|
||||
"invited": "دعاك {name} إلى \"{title}\"",
|
||||
"updated": "حدّث {name} \"{title}\"",
|
||||
"cancelled": "ألغى {name} \"{title}\"",
|
||||
"untitled": "حدث بدون عنوان"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "شخص ما",
|
||||
"shared": "شارك {name} معك {kind} \"{object}\"",
|
||||
"changed": "غيّر {name} صلاحيتك على {kind} \"{object}\"",
|
||||
"revoked": "أزال {name} صلاحيتك على {kind} \"{object}\"",
|
||||
"object": {
|
||||
"folder": "المجلد",
|
||||
"calendar": "التقويم",
|
||||
"address_book": "دفتر العناوين",
|
||||
"files": "مجلد الملفات"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "مشاركة \"{name}\"",
|
||||
"description": "امنح الوصول لمستخدمين أو مجموعات أخرى على هذا الخادم. تُطبَّق التغييرات فورًا.",
|
||||
@@ -3187,6 +3279,8 @@
|
||||
"has_attachment": "المرفقات",
|
||||
"date_after": "بعد",
|
||||
"date_before": "قبل",
|
||||
"size_min": "أكبر من (كيلوبايت)",
|
||||
"size_max": "أصغر من (كيلوبايت)",
|
||||
"starred": "مميز بنجمة",
|
||||
"unread": "غير مقروءة",
|
||||
"read": "مقروءة",
|
||||
@@ -3228,6 +3322,10 @@
|
||||
"hint": "انقر على أي رسالة على اليسار للبدء، أو خذ الجولة أدناه."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "تحرير المستند",
|
||||
"office_loading": "جارٍ فتح محرر المستندات…",
|
||||
"office_error": "تعذر فتح محرر المستندات.",
|
||||
"office_close": "إغلاق المحرر",
|
||||
"open_in_new_tab": "فتح في علامة تبويب جديدة",
|
||||
"title": "الملفات",
|
||||
"search_placeholder": "بحث في الملفات...",
|
||||
@@ -3240,6 +3338,8 @@
|
||||
"rename": "إعادة تسمية",
|
||||
"rename_title": "إعادة تسمية",
|
||||
"new_name": "الاسم الجديد",
|
||||
"name_forbidden_chars": "لا يسمح الخادم بهذه الأحرف في الأسماء: {chars}",
|
||||
"name_reserved": "هذا الاسم محجوز على الخادم.",
|
||||
"delete": "حذف",
|
||||
"delete_confirm_title": "حذف المورد",
|
||||
"delete_confirm_message": "هل أنت متأكد أنك تريد حذف \"{name}\"؟ لا يمكن التراجع عن هذا.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "نسخ رابط الحدث",
|
||||
"copied": "تم نسخ الرابط",
|
||||
"copy_failed": "تعذّر نسخ الرابط"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "جارٍ إكمال التفويض…",
|
||||
"completing_message": "يرجى الانتظار بينما يتم إنشاء الاتصال.",
|
||||
"received_title": "تم استلام التفويض",
|
||||
"received_message": "يمكنك العودة إلى علامة التبويب التي بدأت فيها الاتصال.",
|
||||
"failed_title": "فشل التفويض",
|
||||
"denied": "تم رفض التفويض.",
|
||||
"failed_detail": "فشل التفويض: {error}",
|
||||
"missing_params": "رمز التفويض أو الحالة مفقود.",
|
||||
"delivery_failed": "تعذّر إرسال نتيجة التفويض إلى تبويب التطبيق. يرجى إغلاق هذه النافذة والمحاولة مرة أخرى."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "البحث في كل شيء",
|
||||
"placeholder": "ابحث في البريد وجهات الاتصال والتقويم والملفات...",
|
||||
"scope_label": "البحث في",
|
||||
"account_label": "الحساب",
|
||||
"scope_all": "الكل",
|
||||
"scope_mail": "البريد",
|
||||
"scope_contacts": "جهات الاتصال",
|
||||
"scope_calendar": "التقويم",
|
||||
"scope_files": "الملفات",
|
||||
"all_accounts": "جميع الحسابات",
|
||||
"show_all": "عرض كل النتائج",
|
||||
"no_results": "لا توجد نتائج لـ “{query}”",
|
||||
"server_hint": "الكلمات الكاملة تبحث في الخادم؛ أجزاء الكلمات تطابق العناصر المحمّلة بالفعل",
|
||||
"searching": "جارٍ البحث...",
|
||||
"account_error": "تعذر البحث في {account}",
|
||||
"recent_searches": "عمليات البحث الأخيرة",
|
||||
"recurring": "متكرر",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "ابحث في البريد وجهات الاتصال والأحداث والملفات في جميع الحسابات. جرّب from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "مسطح حسب التاريخ",
|
||||
"grouped": "مجمّع",
|
||||
"load_more": "تحميل المزيد",
|
||||
"preview_empty": "اختر نتيجة لمعاينتها",
|
||||
"open_full": "فتح",
|
||||
"view_label": "العرض"
|
||||
}
|
||||
}
|
||||
|
||||
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "i {count} més",
|
||||
"draft": "Esborrany",
|
||||
"starred": "Destacat",
|
||||
"pinned": "Fixat",
|
||||
"replied": "Respost",
|
||||
"forwarded": "Reenviat",
|
||||
"has_attachment": "Té adjunt",
|
||||
"conversations_count": "{count} de {total} converses",
|
||||
"conversations_count_plus": "{count}+ converses",
|
||||
"conversations_count_simple": "{count} converses",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Reenvia",
|
||||
"delete": "Suprimeix",
|
||||
"archive": "Arxiva",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Marca amb estrella",
|
||||
"unstar": "Treu l'estrella",
|
||||
"mark_unread": "Marca com a no llegit",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Mou",
|
||||
"print": "Imprimeix",
|
||||
"view_source": "Mostra el codi font",
|
||||
|
||||
"fullscreen": "Pantalla completa",
|
||||
|
||||
"exit_fullscreen": "Surt de la pantalla completa",
|
||||
"export_email": "Exporta com a .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Reenvia com a fitxer adjunt",
|
||||
"import_email": "Importa .eml o .zip",
|
||||
"keyboard_shortcuts": "Dreceres de teclat (?)",
|
||||
"email_source": "Codi font del correu",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Confirmació de lectura sol·licitada (feu clic per desactivar-la)",
|
||||
"read_receipt_off": "Sol·licita una confirmació de lectura",
|
||||
"dsn_on": "Notificació de lliurament sol·licitada (fes clic per desactivar)",
|
||||
"dsn_off": "Sol·licita una notificació de lliurament",
|
||||
"format_plain_text": "Canvia a text sense format",
|
||||
"format_rich_text": "Canvia a text enriquit (HTML)",
|
||||
"require_tls_on": "Lliurament xifrat obligatori (fes clic per desactivar)",
|
||||
"require_tls_off": "Exigeix lliurament xifrat (TLS)",
|
||||
"new_message": "Missatge nou",
|
||||
"signature_edit_hint": "Feu doble clic per editar la signatura",
|
||||
"reply": "Respon",
|
||||
@@ -634,11 +643,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Sobreescriu",
|
||||
"toggle_on": "Cancel·la la sobreescriptura",
|
||||
"toggle_tooltip": "Editeu lliurement el nom i l'adreça del remitent. El correu es continua enviant a través de la vostra identitat: només canvia la capçalera «De» visible.",
|
||||
"toggle_tooltip": "Editeu lliurement el nom i l'adreça del remitent. El correu es continua enviant a través de la vostra identitat.",
|
||||
"name_label": "Nom del remitent",
|
||||
"name_placeholder": "Nom",
|
||||
"email_label": "Adreça electrònica del remitent",
|
||||
"email_placeholder": "alias@exemple.com"
|
||||
"email_placeholder": "alias@exemple.com",
|
||||
"envelope_notice": "Si el vostre servidor no accepta aquesta adreça com a remitent del sobre, s'hi fa servir {identity}, i els destinataris la poden veure a la capçalera Return-Path."
|
||||
},
|
||||
"use_template": "Plantilla",
|
||||
"save_as_template": "Desa com a plantilla",
|
||||
@@ -651,6 +661,8 @@
|
||||
"upload_progress": "Pujant {uploaded} / {total}",
|
||||
"upload_cancel": "Cancel·la la pujada",
|
||||
"upload_failed": "No s'ha pogut pujar {filename}",
|
||||
"attachment_too_large": "«{name}» supera la mida que permet el servidor ({max} per fitxer)",
|
||||
"attachments_total_too_large": "Els adjunts superarien el límit del servidor de {max} per missatge",
|
||||
"drop_files": "Deixeu anar els fitxers per adjuntar-los",
|
||||
"show_less": "Mostra'n menys",
|
||||
"send_failed": "No s'ha pogut enviar el correu",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Elimina la columna",
|
||||
"toggle_header_row": "Commuta la fila de capçalera",
|
||||
"delete_table": "Elimina la taula",
|
||||
"pick_size": "Tria la mida"
|
||||
"pick_size": "Tria la mida",
|
||||
"font_size": "Mida de la lletra",
|
||||
"font_size_default": "Per defecte",
|
||||
"background_color": "Color de fons",
|
||||
"remove_background_color": "Elimina el color de fons"
|
||||
},
|
||||
"send_filing_warning": "Enviat, però la neteja posterior a l'enviament ha fallat; és possible que quedi un esborrany obsolet."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "Correu enviat correctament",
|
||||
"email_deleted": "Correu suprimit",
|
||||
"email_archived": "Correu arxivat",
|
||||
"emails_deleted": "{count, plural, one {# correu suprimit} other {# correus suprimits}}",
|
||||
"emails_archived": "{count, plural, one {# correu arxivat} other {# correus arxivats}}",
|
||||
"error_archiving": "No s'ha pogut arxivar el correu",
|
||||
"error_updating": "No s'ha pogut actualitzar el correu",
|
||||
"email_starred": "Correu destacat",
|
||||
"email_unstarred": "S'ha tret l'estrella del correu",
|
||||
"email_marked_read": "Correu marcat com a llegit",
|
||||
@@ -1144,6 +1164,7 @@
|
||||
"status_active": "Actiu en aquest dispositiu",
|
||||
"status_inactive": "No activat en aquest dispositiu",
|
||||
"status_unsupported": "Aquest navegador no admet Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"status_busy": "Treballant…",
|
||||
"enable": "Activa",
|
||||
"reenable": "Torna a registrar",
|
||||
@@ -1181,7 +1202,9 @@
|
||||
"enabled": "Notificacions de correu",
|
||||
"enabled_desc": "Mostra notificacions quan arribin correus nous",
|
||||
"sound": "So de notificació",
|
||||
"sound_desc": "Reprodueix un avís sonor quan arribin correus nous"
|
||||
"sound_desc": "Reprodueix un avís sonor quan arribin correus nous",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Notificacions de calendari",
|
||||
@@ -1241,6 +1264,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Esborra la cerca en canviar de carpeta",
|
||||
"description": "En clicar una carpeta (Safata d'entrada, Enviats…) s'esborra la cerca activa i es mostra aquesta carpeta. Si està desactivat, la cerca es manté i es torna a executar a la carpeta triada."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Sol·licita confirmacions de lectura per defecte",
|
||||
"description": "Activa per defecte la sol·licitud de confirmació de lectura en redactar un missatge nou."
|
||||
@@ -1324,6 +1351,12 @@
|
||||
"label": "Respon des de l'adreça de recepció",
|
||||
"description": "En respondre, envia des de l'adreça a la qual es va enviar originalment el missatge. Primer intenta coincidir amb les identitats; per als lliuraments de captura general de domini, reescriu la capçalera «De» amb l'àlies mentre envia a través de la identitat principal."
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Coincidència de l'adreça de recepció",
|
||||
"description": "Quines adreces de recepció compten com a teves. «Només l'adreça exacta» tria únicament una de les teves identitats configurades; «mateix domini» també tracta qualsevol altra adreça dels teus dominis d'identitat com un àlies catch-all i hi reescriu la capçalera De. Tria l'adreça exacta si aquestes adreces són llistes de distribució.",
|
||||
"exact": "Només l'adreça exacta",
|
||||
"domain": "Qualsevol adreça dels meus dominis"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Posició de la signatura",
|
||||
"description": "On inserir la signatura a les respostes i reenviaments. Abans del text citat es llegeix de manera natural com a tancament de la resposta; després manté el missatge original contigu.",
|
||||
@@ -1753,7 +1786,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "La data de finalització ha de ser posterior a la data d'inici",
|
||||
"start_in_past": "La data d'inici és al passat",
|
||||
"empty_body": "El cos del missatge està buit - els destinataris rebran una resposta en blanc"
|
||||
"empty_body": "El cos del missatge està buit - els destinataris rebran una resposta en blanc",
|
||||
"subject_too_long": "L'assumpte és massa llarg per al servidor (com a màxim {max} bytes).",
|
||||
"body_too_long": "El missatge és massa llarg per al servidor (com a màxim {max} bytes, amb el format inclòs)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1770,6 +1805,8 @@
|
||||
"role_trash": "Paperera",
|
||||
"role_junk": "Correu brossa",
|
||||
"role_archive": "Arxiu",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Cap",
|
||||
"create_folder": "Crea una carpeta",
|
||||
"create_subfolder": "Crea una subcarpeta",
|
||||
@@ -1879,6 +1916,8 @@
|
||||
"title": "Contactes",
|
||||
"description": "Importeu i exporteu els contactes",
|
||||
"group_by_letter_label": "Agrupa per lletra inicial",
|
||||
"sort_by_last_name_label": "Ordena per cognom",
|
||||
"sort_by_last_name_description": "Ordena la llista de contactes per cognom perquè els membres d'una família apareguin junts",
|
||||
"group_by_letter_description": "Mostra capçaleres de secció alfabètiques a la llista de contactes",
|
||||
"import_label": "Importa contactes",
|
||||
"import_description": "Importeu contactes des d'un fitxer vCard (.vcf)",
|
||||
@@ -1970,6 +2009,7 @@
|
||||
"actions": "Accions",
|
||||
"add_action": "Afegeix una acció",
|
||||
"stop_processing": "Atura el processament de les regles següents",
|
||||
"include_spam": "Mou també els missatges marcats com a correu brossa",
|
||||
"attachment_type_placeholder": "p. ex. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Valor (múltiples separats per comes)",
|
||||
"condition_fields": {
|
||||
@@ -2011,6 +2051,8 @@
|
||||
"copy_to_folder": "Selecciona una carpeta",
|
||||
"forward_to": "Reenvia a l'adreça electrònica",
|
||||
"forward_placeholder": "correu@exemple.com",
|
||||
"forward_keep_copy": "Conserva'n una còpia",
|
||||
"forward_limit": "Límit de reenviaments per missatge en aquest servidor: {count}. Els reenviaments addicionals s'ometen.",
|
||||
"reject_message": "Missatge de rebuig",
|
||||
"reject_placeholder": "El vostre correu ha estat rebutjat",
|
||||
"label_name": "Nom de l'etiqueta",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Subcarpeta nova...",
|
||||
"new_folder": "Carpeta nova...",
|
||||
"rename": "Canvia el nom...",
|
||||
"share": "Comparteix...",
|
||||
"import_email": "Importa .eml o .zip...",
|
||||
"empty_folder": "Buida la carpeta",
|
||||
"empty_folder_generic": "Buida la carpeta",
|
||||
@@ -2236,7 +2279,10 @@
|
||||
"toast_error_rename": "No s'ha pogut canviar el nom de la carpeta",
|
||||
"toast_error_delete": "No s'ha pogut suprimir la carpeta",
|
||||
"toast_error_delete_has_children": "La carpeta té subcarpetes. Elimineu-les primer.",
|
||||
"toast_error_delete_has_email": "La carpeta no és buida. Buideu-la primer."
|
||||
"toast_error_delete_has_email": "La carpeta no és buida. Buideu-la primer.",
|
||||
"delete_with_contents_title": "La carpeta no està buida",
|
||||
"delete_with_contents_message": "La carpeta \"{name}\" encara conté {count, plural, one {1 missatge} other {# missatges}}. Vols suprimir la carpeta i tots els seus missatges? No es pot desfer.",
|
||||
"delete_with_contents_confirm": "Suprimeix la carpeta i els missatges"
|
||||
},
|
||||
"shortcuts": {
|
||||
"title": "Dreceres de teclat",
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Enfoca la cerca",
|
||||
"help": "Mostra les dreceres",
|
||||
"refresh": "Actualitza els correus",
|
||||
"select_all": "Selecciona-ho tot"
|
||||
"select_all": "Selecciona-ho tot",
|
||||
"global_search": "Cerca-ho tot"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Desplega/replega el fil"
|
||||
@@ -2722,7 +2769,10 @@
|
||||
"any_month": "Qualsevol mes",
|
||||
"has_email": "Té correu electrònic",
|
||||
"has_phone": "Té telèfon",
|
||||
"has_photo": "Té foto"
|
||||
"has_photo": "Té foto",
|
||||
"sort_by": "Ordena per",
|
||||
"sort_first_name": "Nom",
|
||||
"sort_last_name": "Cognom"
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "Duplica",
|
||||
"today_header": "Avui",
|
||||
"tomorrow_header": "Demà",
|
||||
"agenda_show_earlier": "Mostra esdeveniments anteriors",
|
||||
"agenda_loading": "Carregant…",
|
||||
"agenda_range_start": "Esdeveniments des del {date}",
|
||||
"agenda_range_end": "Esdeveniments fins al {date}",
|
||||
"export_ics": "Exporta com a .ics",
|
||||
"copy_title": "Copia el títol",
|
||||
"copy_link": "Copia l'enllaç de la reunió",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "Color"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Disponibilitat",
|
||||
"free": "Disponible",
|
||||
"busy": "Ocupat",
|
||||
"tentative": "Provisional",
|
||||
"unknown": "No és un usuari d'aquest servidor",
|
||||
"checking": "Comprovant…"
|
||||
},
|
||||
"title": "Participants",
|
||||
"add": "Afegeix un participant",
|
||||
"organizer": "Organitzador",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Tots els esdeveniments",
|
||||
"cancel": "Cancel·la",
|
||||
"save": "Desa",
|
||||
"delete": "Suprimeix"
|
||||
"delete": "Suprimeix",
|
||||
"rsvp_title": "Respon a l'esdeveniment periòdic",
|
||||
"rsvp_description": "Aquest és un esdeveniment periòdic. A quins esdeveniments s'aplica la vostra resposta?",
|
||||
"respond": "Respon"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Recordatoris",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Mostra l'hora dels esdeveniments a la vista mensual del calendari. En pantalles petites, això mostra entrades completes d'esdeveniment en lloc de punts.",
|
||||
"show_week_numbers": "Mostra els números de setmana",
|
||||
"show_week_numbers_desc": "Mostra els números de setmana al minicalendari",
|
||||
"calendar_free_scroll": "Desplaçament lliure",
|
||||
"calendar_free_scroll_desc": "Desplaça't de manera contínua per mesos, setmanes i dies en lloc d'un període cada vegada",
|
||||
"enable_tasks": "Activa les tasques",
|
||||
"enable_tasks_desc": "Mostra una vista de tasques al calendari per gestionar pendents",
|
||||
"show_tasks_on_calendar": "Mostra les tasques al calendari",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "Retard d'1 segon",
|
||||
"hover_preview_delay_2s": "Retard de 2 segons",
|
||||
"hover_preview_off": "Desactivada",
|
||||
"organizer_identity": "Organitza les invitacions com a",
|
||||
"organizer_identity_desc": "L'adreça de calendari usada com a organitzador quan convides gent als esdeveniments.",
|
||||
"organizer_identity_failed": "No s'ha pogut canviar l'adreça de l'organitzador",
|
||||
"show_birthday_calendar": "Calendari d'aniversaris dels contactes",
|
||||
"show_birthday_calendar_desc": "Mostra un calendari virtual amb els aniversaris dels vostres contactes"
|
||||
},
|
||||
@@ -2973,6 +3043,9 @@
|
||||
"alert_now": "Comença ara",
|
||||
"alert_in_minutes": "D'aquí a {count} min",
|
||||
"invitation_sent": "Invitacions enviades",
|
||||
"invitations_denied_title": "No es poden enviar les invitacions",
|
||||
"invitations_denied": "El servidor ha refusat enviar les invitacions: {reason}. Voleu desar l'esdeveniment sense enviar-les?",
|
||||
"save_without_invitations": "Desa sense invitacions",
|
||||
"rsvp_updated": "Resposta actualitzada",
|
||||
"rsvp_error": "No s'ha pogut actualitzar la resposta",
|
||||
"event_duplicated": "Esdeveniment duplicat",
|
||||
@@ -3002,7 +3075,7 @@
|
||||
"supported_formats": "Admet fitxers iCalendar (.ics)",
|
||||
"url_description": "Introduïu l'URL d'un canal iCalendar (.ics) extern per importar-ne els esdeveniments.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "Admet URL de CalDAV i iCalendar (.ics)",
|
||||
"url_hint": "Admet URL d’iCalendar (.ics)",
|
||||
"fetch": "Obtén",
|
||||
"invalid_url": "Introduïu un URL vàlid",
|
||||
"url_fetch_failed": "No s'ha pogut obtenir el calendari des de l'URL",
|
||||
@@ -3149,6 +3222,25 @@
|
||||
"overdue": "Vençuda"
|
||||
}
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Algú",
|
||||
"invited": "{name} t'ha convidat a \"{title}\"",
|
||||
"updated": "{name} ha actualitzat \"{title}\"",
|
||||
"cancelled": "{name} ha cancel·lat \"{title}\"",
|
||||
"untitled": "Esdeveniment sense títol"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Algú",
|
||||
"shared": "{name} ha compartit amb tu {kind} \"{object}\"",
|
||||
"changed": "{name} ha canviat el teu accés a {kind} \"{object}\"",
|
||||
"revoked": "{name} ha retirat el teu accés a {kind} \"{object}\"",
|
||||
"object": {
|
||||
"folder": "la carpeta",
|
||||
"calendar": "el calendari",
|
||||
"address_book": "la llibreta d'adreces",
|
||||
"files": "la carpeta de fitxers"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Comparteix «{name}»",
|
||||
"description": "Concediu accés a altres usuaris o grups d'aquest servidor. Els canvis s'apliquen immediatament.",
|
||||
@@ -3187,6 +3279,8 @@
|
||||
"has_attachment": "Fitxers adjunts",
|
||||
"date_after": "Després de",
|
||||
"date_before": "Abans de",
|
||||
"size_min": "Més gran que (KB)",
|
||||
"size_max": "Més petit que (KB)",
|
||||
"starred": "Destacat",
|
||||
"unread": "No llegit",
|
||||
"read": "Llegit",
|
||||
@@ -3228,6 +3322,10 @@
|
||||
"hint": "Feu clic a qualsevol correu de l'esquerra per començar, o feu la visita guiada de sota."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Edita el document",
|
||||
"office_loading": "S'està obrint l'editor de documents…",
|
||||
"office_error": "No s'ha pogut obrir l'editor de documents.",
|
||||
"office_close": "Tanca l'editor",
|
||||
"open_in_new_tab": "Obre en una pestanya nova",
|
||||
"title": "Fitxers",
|
||||
"search_placeholder": "Cerca fitxers...",
|
||||
@@ -3240,6 +3338,8 @@
|
||||
"rename": "Canvia el nom",
|
||||
"rename_title": "Canvia el nom",
|
||||
"new_name": "Nom nou",
|
||||
"name_forbidden_chars": "El servidor no permet aquests caràcters als noms: {chars}",
|
||||
"name_reserved": "Aquest nom està reservat al servidor.",
|
||||
"delete": "Suprimeix",
|
||||
"delete_confirm_title": "Suprimeix el recurs",
|
||||
"delete_confirm_message": "Segur que voleu suprimir «{name}»? Això no es pot desfer.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Copia l'enllaç de l'esdeveniment",
|
||||
"copied": "Enllaç copiat",
|
||||
"copy_failed": "No s'ha pogut copiar l'enllaç"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Completant l'autorització…",
|
||||
"completing_message": "Espereu mentre s'estableix la connexió.",
|
||||
"received_title": "S'ha rebut l'autorització",
|
||||
"received_message": "Podeu tornar a la pestanya on heu iniciat la connexió.",
|
||||
"failed_title": "Ha fallat l'autorització",
|
||||
"denied": "Sha denegat lautorització.",
|
||||
"failed_detail": "Ha fallat lautorització: {error}",
|
||||
"missing_params": "Falta el codi dautorització o lestat.",
|
||||
"delivery_failed": "No sha pogut lliurar el resultat de lautorització a la pestanya de laplicació. Tanqueu aquesta finestra i torneu-ho a provar."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Cerca-ho tot",
|
||||
"placeholder": "Cerca al correu, contactes, calendari, fitxers...",
|
||||
"scope_label": "Cerca a",
|
||||
"account_label": "Compte",
|
||||
"scope_all": "Tot",
|
||||
"scope_mail": "Correu",
|
||||
"scope_contacts": "Contactes",
|
||||
"scope_calendar": "Calendari",
|
||||
"scope_files": "Fitxers",
|
||||
"all_accounts": "Tots els comptes",
|
||||
"show_all": "Mostra tots els resultats",
|
||||
"no_results": "Cap resultat per a “{query}”",
|
||||
"server_hint": "Les paraules completes cerquen al servidor; les parcials coincideixen amb els elements ja carregats",
|
||||
"searching": "Cercant...",
|
||||
"account_error": "No s'ha pogut cercar a {account}",
|
||||
"recent_searches": "Cerques recents",
|
||||
"recurring": "Periòdic",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Cerca correu, contactes, esdeveniments i fitxers a tots els comptes. Prova from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Pla per data",
|
||||
"grouped": "Agrupat",
|
||||
"load_more": "Carrega'n més",
|
||||
"preview_empty": "Seleccioneu un resultat per previsualitzar-lo",
|
||||
"open_full": "Obre",
|
||||
"view_label": "Vista"
|
||||
}
|
||||
}
|
||||
|
||||
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "a {count} dalších",
|
||||
"draft": "Koncept",
|
||||
"starred": "S hvězdičkou",
|
||||
"pinned": "Připnuto",
|
||||
"replied": "Zodpovězeno",
|
||||
"forwarded": "Přeposláno",
|
||||
"has_attachment": "Obsahuje přílohu",
|
||||
"conversations_count": "{count} z {total} konverzací",
|
||||
"conversations_count_plus": "Více než {count} konverzací",
|
||||
"conversations_count_simple": "{count} konverzací",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Přeposlat",
|
||||
"delete": "Odstranit",
|
||||
"archive": "Archivovat",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Označit hvězdičkou",
|
||||
"unstar": "Odebrat hvězdičku",
|
||||
"mark_unread": "Označit jako nepřečtené",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Přesunout",
|
||||
"print": "Tisk",
|
||||
"view_source": "Zobrazit zdrojový kód",
|
||||
|
||||
"fullscreen": "Celá obrazovka",
|
||||
|
||||
"exit_fullscreen": "Ukončit režim celé obrazovky",
|
||||
"export_email": "Exportovat jako .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Přeposlat jako přílohu",
|
||||
"import_email": "Importovat .eml nebo .zip",
|
||||
"keyboard_shortcuts": "Klávesové zkratky (?)",
|
||||
"email_source": "Zdrojový kód zprávy",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Vyžádáno potvrzení o přečtení (kliknutím vypnete)",
|
||||
"read_receipt_off": "Vyžádat potvrzení o přečtení",
|
||||
"dsn_on": "Oznámení o doručení vyžádáno (kliknutím vypnete)",
|
||||
"dsn_off": "Vyžádat oznámení o doručení",
|
||||
"format_plain_text": "Přepnout na prostý text",
|
||||
"format_rich_text": "Přepnout na formátovaný text (HTML)",
|
||||
"require_tls_on": "Šifrované doručení vyžadováno (kliknutím vypnete)",
|
||||
"require_tls_off": "Vyžadovat šifrované doručení (TLS)",
|
||||
"new_message": "Nová zpráva",
|
||||
"signature_edit_hint": "Dvojklikem upravíte podpis",
|
||||
"reply": "Odpovědět",
|
||||
@@ -631,11 +640,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Přepsat",
|
||||
"toggle_on": "Zrušit přepsání",
|
||||
"toggle_tooltip": "Volně upravujte jméno a adresu odesílatele. Pošta se stále odesílá přes vaši identitu - mění se pouze viditelné záhlaví Od.",
|
||||
"toggle_tooltip": "Volně upravujte jméno a adresu odesílatele. Pošta se stále odesílá přes vaši identitu.",
|
||||
"name_label": "Jméno odesílatele",
|
||||
"name_placeholder": "Jméno",
|
||||
"email_label": "E-mailová adresa odesílatele",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "Pokud váš server tuto adresu jako odesílatele obálky nepřijme, použije se místo ní {identity} a příjemci ji uvidí v záhlaví Return-Path."
|
||||
},
|
||||
"use_template": "Šablona",
|
||||
"save_as_template": "Uložit jako šablonu",
|
||||
@@ -648,6 +658,8 @@
|
||||
"upload_progress": "Nahrávání {uploaded} / {total}",
|
||||
"upload_cancel": "Zrušit nahrávání",
|
||||
"upload_failed": "Soubor {filename} se nepodařilo nahrát",
|
||||
"attachment_too_large": "„{name}“ je větší, než server povoluje ({max} na soubor)",
|
||||
"attachments_total_too_large": "Přílohy by překročily limit serveru {max} na zprávu",
|
||||
"drop_files": "Přetáhněte soubory sem a připojte je",
|
||||
"show_less": "Zobrazit méně",
|
||||
"send_failed": "Odeslání zprávy selhalo",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Odstranit sloupec",
|
||||
"toggle_header_row": "Přepnout řádek záhlaví",
|
||||
"delete_table": "Odstranit tabulku",
|
||||
"pick_size": "Vybrat velikost"
|
||||
"pick_size": "Vybrat velikost",
|
||||
"font_size": "Velikost písma",
|
||||
"font_size_default": "Výchozí",
|
||||
"background_color": "Barva pozadí",
|
||||
"remove_background_color": "Odebrat barvu pozadí"
|
||||
},
|
||||
"send_filing_warning": "Odesláno - ale následný úklid selhal, může zůstat zastaralý koncept."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "Zpráva byla úspěšně odeslána",
|
||||
"email_deleted": "Zpráva byla odstraněna",
|
||||
"email_archived": "Zpráva byla archivována",
|
||||
"emails_deleted": "{count, plural, one {# e-mail smazán} few {# e-maily smazány} other {# e-mailů smazáno}}",
|
||||
"emails_archived": "{count, plural, one {# e-mail archivován} few {# e-maily archivovány} other {# e-mailů archivováno}}",
|
||||
"error_archiving": "E-mail se nepodařilo archivovat",
|
||||
"error_updating": "E-mail se nepodařilo aktualizovat",
|
||||
"email_starred": "Zpráva byla označena hvězdičkou",
|
||||
"email_unstarred": "Hvězdička byla odebrána",
|
||||
"email_marked_read": "Zpráva byla označena jako přečtená",
|
||||
@@ -1143,7 +1163,9 @@
|
||||
"enabled": "E-mailová oznámení",
|
||||
"enabled_desc": "Zobrazovat oznámení při příchodu nových e-mailů",
|
||||
"sound": "Zvuk oznámení",
|
||||
"sound_desc": "Přehrát zvukové upozornění při příchodu nových e-mailů"
|
||||
"sound_desc": "Přehrát zvukové upozornění při příchodu nových e-mailů",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Oznámení kalendáře",
|
||||
@@ -1172,6 +1194,7 @@
|
||||
"status_busy": "Pracuji…",
|
||||
"status_inactive": "Není povoleno na tomto zařízení",
|
||||
"status_unsupported": "Tento prohlížeč nepodporuje Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"title": "Oznámení na pozadí",
|
||||
"devices_title": "Registrovaná zařízení",
|
||||
"devices_desc": "Všechna zařízení registrovaná pro příjem oznamení na pozadí u tohoto účtu. Odvolání okamžitě zastaví jejich příjem.",
|
||||
@@ -1238,6 +1261,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Vymazat hledání při přepnutí složky",
|
||||
"description": "Kliknutí na složku (Doručená pošta, Odeslaná…) vymaže aktivní hledání a zobrazí složku. Když je vypnuto, hledání zůstává a spustí se znovu ve zvolené složce."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Standardně vyžadovat potvrzení o přečtení",
|
||||
"description": "Při psaní nové zprávy předem zapnout žádost o potvrzení o přečtení."
|
||||
@@ -1321,6 +1348,12 @@
|
||||
"label": "Automaticky vybírat adresu pro odpověď",
|
||||
"description": "Při odpovídání automaticky přepnout adresu odesílatele na identitu, která původně obdržela zprávu"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Porovnávání přijímací adresy",
|
||||
"description": "Které přijímací adresy se počítají jako vaše. „Pouze přesná adresa“ vybere jen jednu z vašich nastavených identit; „stejná doména“ navíc považuje jakoukoli jinou adresu na doménách vašich identit za catch-all alias a přepíše na ni hlavičku Od. Pokud jsou tyto adresy distribuční seznamy, zvolte přesnou adresu.",
|
||||
"exact": "Pouze přesná adresa",
|
||||
"domain": "Jakákoli adresa na mých doménách"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Pozice podpisu",
|
||||
"description": "Kam vložit podpis v odpovědích a přeposláních. Nad citovaným textem působí přirozeně jako zakončení odpovědi; pod ním zachovává původní zprávu vcelku.",
|
||||
@@ -1746,7 +1779,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "Datum ukončení musí být po datu zahájení",
|
||||
"start_in_past": "Datum zahájení je v minulosti",
|
||||
"empty_body": "Tělo zprávy je prázdné - příjemci obdrží prázdnou odpověď"
|
||||
"empty_body": "Tělo zprávy je prázdné - příjemci obdrží prázdnou odpověď",
|
||||
"subject_too_long": "Předmět je pro server příliš dlouhý (nejvýše {max} bajtů).",
|
||||
"body_too_long": "Zpráva je pro server příliš dlouhá (nejvýše {max} bajtů včetně formátování)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1763,6 +1798,8 @@
|
||||
"role_trash": "Koš",
|
||||
"role_junk": "Spam / Nevyžádaná pošta",
|
||||
"role_archive": "Archiv",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Žádná",
|
||||
"create_folder": "Vytvořit složku",
|
||||
"create_subfolder": "Vytvořit podsložku",
|
||||
@@ -1872,6 +1909,8 @@
|
||||
"title": "Kontakty",
|
||||
"description": "Import a export kontaktů",
|
||||
"group_by_letter_label": "Seskupit podle prvního písmena",
|
||||
"sort_by_last_name_label": "Řadit podle příjmení",
|
||||
"sort_by_last_name_description": "Seřadit seznam kontaktů podle příjmení, aby členové rodiny byli pohromadě",
|
||||
"group_by_letter_description": "Zobrazit záhlaví sekcí podle abecedy v seznamu kontaktů",
|
||||
"import_label": "Importovat kontakty",
|
||||
"import_description": "Importovat kontakty ze souboru vCard (.vcf)",
|
||||
@@ -1963,6 +2002,7 @@
|
||||
"actions": "Akce",
|
||||
"add_action": "Přidat akci",
|
||||
"stop_processing": "Zastavit zpracování dalších pravidel",
|
||||
"include_spam": "Přesouvat i zprávy označené jako spam",
|
||||
"attachment_type_placeholder": "např. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Hodnota (více oddělených čárkami)",
|
||||
"condition_fields": {
|
||||
@@ -2004,6 +2044,8 @@
|
||||
"copy_to_folder": "Vyberte složku",
|
||||
"forward_to": "Přeposlat na e-mailovou adresu",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "Ponechat kopii",
|
||||
"forward_limit": "Limit přeposlání na zprávu na tomto serveru: {count}. Další přeposlání se přeskočí.",
|
||||
"reject_message": "Zpráva o odmítnutí",
|
||||
"reject_placeholder": "Váš e-mail byl odmítnut",
|
||||
"label_name": "Název štítku",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Nová podsložka...",
|
||||
"new_folder": "Nová složka...",
|
||||
"rename": "Přejmenovat...",
|
||||
"share": "Sdílet...",
|
||||
"import_email": "Importovat .eml nebo .zip...",
|
||||
"empty_folder": "Vyprázdnit složku",
|
||||
"empty_folder_generic": "Vyprázdnit složku",
|
||||
@@ -2236,7 +2279,10 @@
|
||||
"toast_error_rename": "Nepodařilo se přejmenovat složku",
|
||||
"toast_error_delete": "Nepodařilo se smazat složku",
|
||||
"toast_error_delete_has_children": "Složka obsahuje podsložky. Nejprve je odstraňte.",
|
||||
"toast_error_delete_has_email": "Složka není prázdná. Nejprve ji vyprázdněte."
|
||||
"toast_error_delete_has_email": "Složka není prázdná. Nejprve ji vyprázdněte.",
|
||||
"delete_with_contents_title": "Složka není prázdná",
|
||||
"delete_with_contents_message": "Složka „{name}“ stále obsahuje {count, plural, one {1 zprávu} few {# zprávy} other {# zpráv}}. Smazat složku i všechny její zprávy? Tuto akci nelze vrátit.",
|
||||
"delete_with_contents_confirm": "Smazat složku i zprávy"
|
||||
},
|
||||
"shortcuts": {
|
||||
"title": "Klávesové zkratky",
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Přejít do hledání",
|
||||
"help": "Zobrazit zkratky",
|
||||
"refresh": "Obnovit zprávy",
|
||||
"select_all": "Vybrat vše"
|
||||
"select_all": "Vybrat vše",
|
||||
"global_search": "Hledat ve všem"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Rozbalit/sbalit vlákno"
|
||||
@@ -2721,7 +2768,10 @@
|
||||
"any_month": "Kterýkoli měsíc",
|
||||
"has_email": "Má e-mail",
|
||||
"has_phone": "Má telefon",
|
||||
"has_photo": "Má fotku"
|
||||
"has_photo": "Má fotku",
|
||||
"sort_by": "Řadit podle",
|
||||
"sort_first_name": "Jméno",
|
||||
"sort_last_name": "Příjmení"
|
||||
},
|
||||
"open_categories": "Otevřít kategorie"
|
||||
},
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "Duplikovat",
|
||||
"today_header": "Dnes",
|
||||
"tomorrow_header": "Zítra",
|
||||
"agenda_show_earlier": "Zobrazit dřívější události",
|
||||
"agenda_loading": "Načítání…",
|
||||
"agenda_range_start": "Události od {date}",
|
||||
"agenda_range_end": "Události do {date}",
|
||||
"export_ics": "Exportovat jako .ics",
|
||||
"copy_title": "Kopírovat název",
|
||||
"copy_link": "Kopírovat odkaz na schůzku",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "Barva"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Dostupnost",
|
||||
"free": "Volný",
|
||||
"busy": "Zaneprázdněn",
|
||||
"tentative": "Předběžně",
|
||||
"unknown": "Není uživatelem tohoto serveru",
|
||||
"checking": "Ověřuje se…"
|
||||
},
|
||||
"title": "Účastníci",
|
||||
"add": "Přidat účastníka",
|
||||
"organizer": "Organizátor",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Všechny události",
|
||||
"cancel": "Zrušit",
|
||||
"save": "Uložit",
|
||||
"delete": "Odstranit"
|
||||
"delete": "Odstranit",
|
||||
"rsvp_title": "Odpovědět na opakující se událost",
|
||||
"rsvp_description": "Toto je opakující se událost. Na které události se vaše odpověď vztahuje?",
|
||||
"respond": "Odpovědět"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Připomenutí",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Zobrazovat časy událostí v měsíčním zobrazení kalendáře",
|
||||
"show_week_numbers": "Zobrazit čísla týdnů",
|
||||
"show_week_numbers_desc": "Zobrazovat čísla týdnů v minikalendáři",
|
||||
"calendar_free_scroll": "Volné posouvání",
|
||||
"calendar_free_scroll_desc": "Plynule procházet měsíce, týdny a dny místo jednoho období najednou",
|
||||
"enable_tasks": "Povolit úkoly",
|
||||
"enable_tasks_desc": "Zobrazovat zobrazení úkolů v kalendáři pro správu úkolů",
|
||||
"show_tasks_on_calendar": "Zobrazit úkoly v kalendáři",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "Zpoždění 1 sekunda",
|
||||
"hover_preview_delay_2s": "Zpoždění 2 sekundy",
|
||||
"hover_preview_off": "Vypnuto",
|
||||
"organizer_identity": "Organizovat pozvánky jako",
|
||||
"organizer_identity_desc": "Adresa kalendáře použitá jako organizátor, když zvete lidi na události.",
|
||||
"organizer_identity_failed": "Nepodařilo se změnit adresu organizátora",
|
||||
"show_birthday_calendar": "Kalendář narozenin",
|
||||
"show_birthday_calendar_desc": "Zobrazovat virtuální kalendář s narozeninami z vašich kontaktů"
|
||||
},
|
||||
@@ -2947,6 +3017,9 @@
|
||||
"alert_now": "Začíná nyní",
|
||||
"alert_in_minutes": "Za {count} min",
|
||||
"invitation_sent": "Pozvánky byly odeslány",
|
||||
"invitations_denied_title": "Pozvánky nelze odeslat",
|
||||
"invitations_denied": "Server odmítl odeslat pozvánky: {reason}. Uložit událost bez jejich odeslání?",
|
||||
"save_without_invitations": "Uložit bez pozvánek",
|
||||
"rsvp_updated": "Odpověď byla aktualizována",
|
||||
"rsvp_error": "Aktualizace odpovědi selhala",
|
||||
"event_duplicated": "Událost byla duplikována",
|
||||
@@ -2975,7 +3048,7 @@
|
||||
"supported_formats": "Podporuje soubory iCalendar (.ics)",
|
||||
"url_description": "Zadejte adresu URL externího zdroje iCalendar (.ics) pro import událostí.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "Podporuje adresy URL CalDAV a iCalendar (.ics)",
|
||||
"url_hint": "Podporuje adresy URL iCalendar (.ics)",
|
||||
"fetch": "Načíst",
|
||||
"invalid_url": "Zadejte platnou adresu URL",
|
||||
"url_fetch_failed": "Načtení kalendáře z adresy URL selhalo",
|
||||
@@ -3164,6 +3237,8 @@
|
||||
"has_attachment": "Obsahuje přílohu",
|
||||
"date_after": "Po",
|
||||
"date_before": "Před",
|
||||
"size_min": "Větší než (KB)",
|
||||
"size_max": "Menší než (KB)",
|
||||
"starred": "S hvězdičkou",
|
||||
"unread": "Nepřečtené",
|
||||
"read": "Přečtené",
|
||||
@@ -3205,6 +3280,10 @@
|
||||
"hint": "Klikněte na libovolnou zprávu vlevo pro začátek, nebo využijte průvodce níže."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Upravit dokument",
|
||||
"office_loading": "Otevírání editoru dokumentů…",
|
||||
"office_error": "Editor dokumentů se nepodařilo otevřít.",
|
||||
"office_close": "Zavřít editor",
|
||||
"open_in_new_tab": "Otevřít na nové kartě",
|
||||
"title": "Soubory",
|
||||
"search_placeholder": "Hledat soubory...",
|
||||
@@ -3217,6 +3296,8 @@
|
||||
"rename": "Přejmenovat",
|
||||
"rename_title": "Přejmenovat",
|
||||
"new_name": "Nový název",
|
||||
"name_forbidden_chars": "Server nepovoluje v názvech tyto znaky: {chars}",
|
||||
"name_reserved": "Tento název je na serveru vyhrazen.",
|
||||
"delete": "Odstranit",
|
||||
"delete_confirm_title": "Odstranit položku",
|
||||
"delete_confirm_message": "Opravdu chcete odstranit \"{name}\"? Tuto akci nelze vrátit zpět.",
|
||||
@@ -3451,6 +3532,25 @@
|
||||
"unified_mailbox": {
|
||||
"search_unavailable": "Vyhledávání není ve sjednoceném zobrazení k dispozici"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Někdo",
|
||||
"invited": "{name} vás pozval(a) na „{title}“",
|
||||
"updated": "{name} aktualizoval(a) „{title}“",
|
||||
"cancelled": "{name} zrušil(a) „{title}“",
|
||||
"untitled": "Událost bez názvu"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Někdo",
|
||||
"shared": "{name} s vámi sdílí {kind} „{object}“",
|
||||
"changed": "{name} změnil(a) váš přístup k {kind} „{object}“",
|
||||
"revoked": "{name} odebral(a) váš přístup k {kind} „{object}“",
|
||||
"object": {
|
||||
"folder": "složku",
|
||||
"calendar": "kalendář",
|
||||
"address_book": "adresář",
|
||||
"files": "složku souborů"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Sdílet „{name}\"",
|
||||
"description": "Udělte přístup dalším uživatelům nebo skupinám na tomto serveru. Změny se projeví okamžitě.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Kopírovat odkaz na událost",
|
||||
"copied": "Odkaz zkopírován",
|
||||
"copy_failed": "Odkaz se nepodařilo zkopírovat"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Dokončování autorizace…",
|
||||
"completing_message": "Počkejte prosím, než se naváže spojení.",
|
||||
"received_title": "Autorizace přijata",
|
||||
"received_message": "Můžete se vrátit na kartu, kde jste zahájili připojení.",
|
||||
"failed_title": "Autorizace se nezdařila",
|
||||
"denied": "Autorizace byla zamítnuta.",
|
||||
"failed_detail": "Autorizace se nezdařila: {error}",
|
||||
"missing_params": "Chybí autorizační kód nebo stav.",
|
||||
"delivery_failed": "Výsledek autorizace se nepodařilo předat kartě aplikace. Zavřete toto okno a zkuste to znovu."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Hledat ve všem",
|
||||
"placeholder": "Hledat v poště, kontaktech, kalendáři, souborech...",
|
||||
"scope_label": "Hledat v",
|
||||
"account_label": "Účet",
|
||||
"scope_all": "Vše",
|
||||
"scope_mail": "Pošta",
|
||||
"scope_contacts": "Kontakty",
|
||||
"scope_calendar": "Kalendář",
|
||||
"scope_files": "Soubory",
|
||||
"all_accounts": "Všechny účty",
|
||||
"show_all": "Zobrazit všechny výsledky",
|
||||
"no_results": "Žádné výsledky pro „{query}“",
|
||||
"server_hint": "Celá slova prohledávají server; části slov odpovídají již načteným položkám",
|
||||
"searching": "Hledání...",
|
||||
"account_error": "Nepodařilo se prohledat {account}",
|
||||
"recent_searches": "Nedávná hledání",
|
||||
"recurring": "Opakovaná",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Hledejte poštu, kontakty, události a soubory ve všech účtech. Zkuste from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Ploše podle data",
|
||||
"grouped": "Seskupené",
|
||||
"load_more": "Načíst další",
|
||||
"preview_empty": "Vyberte výsledek pro náhled",
|
||||
"open_full": "Otevřít",
|
||||
"view_label": "Zobrazení"
|
||||
}
|
||||
}
|
||||
|
||||
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "og {count} andre",
|
||||
"draft": "Kladde",
|
||||
"starred": "Stjernemarkerede",
|
||||
"pinned": "Fastgjort",
|
||||
"replied": "Besvaret",
|
||||
"forwarded": "Videresendt",
|
||||
"has_attachment": "Har vedhæftning",
|
||||
"conversations_count": "{count} af {total} samtaler",
|
||||
"conversations_count_plus": "{count}+ samtaler",
|
||||
"conversations_count_simple": "{count} samtaler",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Videresend",
|
||||
"delete": "Slet",
|
||||
"archive": "Arkivér",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Stjernemarkér",
|
||||
"unstar": "Fjern stjernemarkering",
|
||||
"mark_unread": "Markér som ulæst",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Flyt",
|
||||
"print": "Udskriv",
|
||||
"view_source": "Vis kilde",
|
||||
|
||||
"fullscreen": "Fuld skærm",
|
||||
|
||||
"exit_fullscreen": "Afslut fuld skærm",
|
||||
"export_email": "Eksportér som .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Videresend som vedhæftet fil",
|
||||
"import_email": "Importér .eml eller .zip",
|
||||
"keyboard_shortcuts": "Tastaturgenveje (?)",
|
||||
"email_source": "E-mail-kilde",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Læsekvittering anmodet (klik for at deaktivere)",
|
||||
"read_receipt_off": "Anmod om læsekvittering",
|
||||
"dsn_on": "Leveringsbesked anmodet (klik for at slå fra)",
|
||||
"dsn_off": "Anmod om leveringsbesked",
|
||||
"format_plain_text": "Skift til ren tekst",
|
||||
"format_rich_text": "Skift til formateret tekst (HTML)",
|
||||
"require_tls_on": "Krypteret levering påkrævet (klik for at slå fra)",
|
||||
"require_tls_off": "Kræv krypteret levering (TLS)",
|
||||
"new_message": "Ny besked",
|
||||
"signature_edit_hint": "Dobbeltklik for at redigere signaturen",
|
||||
"reply": "Svar",
|
||||
@@ -633,11 +642,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Tilsidesæt",
|
||||
"toggle_on": "Annuller tilsidesættelse",
|
||||
"toggle_tooltip": "Redigér Fra-navn og -adresse frit. Mail sendes stadig gennem din identitet - kun den synlige Fra-header ændres.",
|
||||
"toggle_tooltip": "Redigér Fra-navn og -adresse frit. Mail sendes stadig gennem din identitet.",
|
||||
"name_label": "Fra-navn",
|
||||
"name_placeholder": "Navn",
|
||||
"email_label": "Fra-e-mailadresse",
|
||||
"email_placeholder": "alias@eksempel.dk"
|
||||
"email_placeholder": "alias@eksempel.dk",
|
||||
"envelope_notice": "Hvis din server ikke accepterer denne adresse som konvolutafsender, bruges {identity} i stedet, og modtagerne kan se den i Return-Path-headeren."
|
||||
},
|
||||
"use_template": "Skabelon",
|
||||
"save_as_template": "Gem som skabelon",
|
||||
@@ -650,6 +660,8 @@
|
||||
"upload_progress": "Uploader {uploaded} / {total}",
|
||||
"upload_cancel": "Annuller upload",
|
||||
"upload_failed": "Kunne ikke uploade {filename}",
|
||||
"attachment_too_large": "\"{name}\" er større, end serveren tillader ({max} pr. fil)",
|
||||
"attachments_total_too_large": "Vedhæftningerne ville overskride serverens grænse på {max} pr. besked",
|
||||
"drop_files": "Træk filer hertil for at vedhæfte",
|
||||
"show_less": "Vis mindre",
|
||||
"send_failed": "Kunne ikke sende e-mail",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Slet kolonne",
|
||||
"toggle_header_row": "Slå overskriftsrække til/fra",
|
||||
"delete_table": "Slet tabel",
|
||||
"pick_size": "Vælg størrelse"
|
||||
"pick_size": "Vælg størrelse",
|
||||
"font_size": "Skriftstørrelse",
|
||||
"font_size_default": "Standard",
|
||||
"background_color": "Baggrundsfarve",
|
||||
"remove_background_color": "Fjern baggrundsfarve"
|
||||
},
|
||||
"send_filing_warning": "Sendt - men oprydningen bagefter mislykkedes, en forældet kladde kan blive stående."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "E-mail sendt succesfuldt",
|
||||
"email_deleted": "E-mail slettet",
|
||||
"email_archived": "E-mail arkiveret",
|
||||
"emails_deleted": "{count, plural, one {# e-mail slettet} other {# e-mails slettet}}",
|
||||
"emails_archived": "{count, plural, one {# e-mail arkiveret} other {# e-mails arkiveret}}",
|
||||
"error_archiving": "Kunne ikke arkivere e-mail",
|
||||
"error_updating": "Kunne ikke opdatere e-mail",
|
||||
"email_starred": "E-mail stjernemarkeret",
|
||||
"email_unstarred": "Stjernemarkering fjernet",
|
||||
"email_marked_read": "E-mail markeret som læst",
|
||||
@@ -1144,6 +1164,7 @@
|
||||
"status_active": "Aktiv på denne enhed",
|
||||
"status_inactive": "Ikke aktiveret på denne enhed",
|
||||
"status_unsupported": "Denne browser understøtter ikke Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"status_busy": "Arbejder…",
|
||||
"enable": "Aktivér",
|
||||
"reenable": "Genregistrér",
|
||||
@@ -1181,7 +1202,9 @@
|
||||
"enabled": "E-mail-notifikationer",
|
||||
"enabled_desc": "Vis notifikationer, når nye e-mails ankommer",
|
||||
"sound": "Notifikationslyd",
|
||||
"sound_desc": "Afspil en lydadvarsel, når nye e-mails ankommer"
|
||||
"sound_desc": "Afspil en lydadvarsel, når nye e-mails ankommer",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Kalendernotifikationer",
|
||||
@@ -1241,6 +1264,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Ryd søgningen ved skift af mappe",
|
||||
"description": "Klik på en mappe (Indbakke, Sendt …) rydder den aktive søgning og viser mappen. Når det er slået fra, bevares søgningen og køres igen i den valgte mappe."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Anmod om læsekvittering som standard",
|
||||
"description": "Aktivér anmodningen om læsekvittering på forhånd, når du skriver en ny besked."
|
||||
@@ -1324,6 +1351,12 @@
|
||||
"label": "Svar fra modtaget adresse",
|
||||
"description": "Når du svarer, send fra den adresse som beskeden oprindeligt blev sendt til. Matcher først identiteter; for domæne catch-all-leveringer omskriver Fra-headeren til aliaset, mens der sendes gennem din primære identitet."
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Matchning af modtageradresse",
|
||||
"description": "Hvilke modtageradresser der tæller som dine. „Kun præcis adresse“ vælger udelukkende en af dine opsatte identiteter; „samme domæne“ behandler desuden enhver anden adresse på dine identitetsdomæner som et catch-all-alias og omskriver Fra-headeren til den. Vælg præcis adresse, hvis disse adresser er distributionslister.",
|
||||
"exact": "Kun præcis adresse",
|
||||
"domain": "Enhver adresse på mine domæner"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Signaturplacering",
|
||||
"description": "Hvor din signatur indsættes i svar og videresendelser. Før citeret tekst læses naturligt som en afslutning på svaret; under bevarer den oprindelige besked sammenhængende.",
|
||||
@@ -1749,7 +1782,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "Slutdato skal være efter startdato",
|
||||
"start_in_past": "Startdato er tidligere end i dag",
|
||||
"empty_body": "Beskedteksten er tom - modtagere vil modtage et tomt svar"
|
||||
"empty_body": "Beskedteksten er tom - modtagere vil modtage et tomt svar",
|
||||
"subject_too_long": "Emnet er for langt til serveren (højst {max} byte).",
|
||||
"body_too_long": "Beskeden er for lang til serveren (højst {max} byte inkl. formatering)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1766,6 +1801,8 @@
|
||||
"role_trash": "Papirkurv",
|
||||
"role_junk": "Spam",
|
||||
"role_archive": "Arkiv",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Ingen",
|
||||
"create_folder": "Opret mappe",
|
||||
"create_subfolder": "Opret undermappe",
|
||||
@@ -1875,6 +1912,8 @@
|
||||
"title": "Kontakter",
|
||||
"description": "Importér og eksportér dine kontakter",
|
||||
"group_by_letter_label": "Gruppér efter første bogstav",
|
||||
"sort_by_last_name_label": "Sortér efter efternavn",
|
||||
"sort_by_last_name_description": "Sortér kontaktlisten efter efternavn, så familiemedlemmer står samlet",
|
||||
"group_by_letter_description": "Vis alfabetiske sektionsoverskrifter i kontaktlisten",
|
||||
"import_label": "Importér kontakter",
|
||||
"import_description": "Importér kontakter fra en vCard-fil (.vcf)",
|
||||
@@ -1966,6 +2005,7 @@
|
||||
"actions": "Handlinger",
|
||||
"add_action": "Tilføj handling",
|
||||
"stop_processing": "Stop behandling af efterfølgende regler",
|
||||
"include_spam": "Flyt også beskeder, der er markeret som spam",
|
||||
"attachment_type_placeholder": "f.eks. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Værdi (flere adskilt med komma)",
|
||||
"condition_fields": {
|
||||
@@ -2007,6 +2047,8 @@
|
||||
"copy_to_folder": "Vælg mappe",
|
||||
"forward_to": "Videresend til e-mailadresse",
|
||||
"forward_placeholder": "email@eksempel.dk",
|
||||
"forward_keep_copy": "Behold en kopi",
|
||||
"forward_limit": "Grænse for videresendelser pr. besked på denne server: {count}. Yderligere videresendelser springes over.",
|
||||
"reject_message": "Afvisningsbesked",
|
||||
"reject_placeholder": "Din e-mail er blevet afvist",
|
||||
"label_name": "Tag-navn",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Ny undermappe...",
|
||||
"new_folder": "Ny mappe...",
|
||||
"rename": "Omdøb...",
|
||||
"share": "Del...",
|
||||
"import_email": "Importér .eml eller .zip...",
|
||||
"empty_folder": "Tøm mappe",
|
||||
"empty_folder_generic": "Tøm mappe",
|
||||
@@ -2236,7 +2279,10 @@
|
||||
"toast_error_rename": "Kunne ikke omdøbe mappe",
|
||||
"toast_error_delete": "Kunne ikke slette mappe",
|
||||
"toast_error_delete_has_children": "Mappen har undermapper. Fjern dem først.",
|
||||
"toast_error_delete_has_email": "Mappen er ikke tom. Tøm den først."
|
||||
"toast_error_delete_has_email": "Mappen er ikke tom. Tøm den først.",
|
||||
"delete_with_contents_title": "Mappen er ikke tom",
|
||||
"delete_with_contents_message": "Mappen \"{name}\" indeholder stadig {count, plural, one {1 besked} other {# beskeder}}. Slet mappen og alle dens beskeder? Dette kan ikke fortrydes.",
|
||||
"delete_with_contents_confirm": "Slet mappe og beskeder"
|
||||
},
|
||||
"shortcuts": {
|
||||
"title": "Tastaturgenveje",
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Fokusér søgning",
|
||||
"help": "Vis genveje",
|
||||
"refresh": "Opdatér e-mails",
|
||||
"select_all": "Vælg alle"
|
||||
"select_all": "Vælg alle",
|
||||
"global_search": "Søg i alt"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Udvid/skjul tråd"
|
||||
@@ -2721,7 +2768,10 @@
|
||||
"any_month": "Enhver måned",
|
||||
"has_email": "Har e-mail",
|
||||
"has_phone": "Har telefon",
|
||||
"has_photo": "Har billede"
|
||||
"has_photo": "Har billede",
|
||||
"sort_by": "Sortér efter",
|
||||
"sort_first_name": "Fornavn",
|
||||
"sort_last_name": "Efternavn"
|
||||
},
|
||||
"open_categories": "Åbn kategorier"
|
||||
},
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "Duplikér",
|
||||
"today_header": "I dag",
|
||||
"tomorrow_header": "I morgen",
|
||||
"agenda_show_earlier": "Vis tidligere begivenheder",
|
||||
"agenda_loading": "Indlæser…",
|
||||
"agenda_range_start": "Begivenheder fra {date}",
|
||||
"agenda_range_end": "Begivenheder til {date}",
|
||||
"export_ics": "Eksportér som .ics",
|
||||
"copy_title": "Kopier titel",
|
||||
"copy_link": "Kopier mødelink",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "Farve"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Tilgængelighed",
|
||||
"free": "Ledig",
|
||||
"busy": "Optaget",
|
||||
"tentative": "Foreløbig",
|
||||
"unknown": "Ikke en bruger på denne server",
|
||||
"checking": "Kontrollerer…"
|
||||
},
|
||||
"title": "Deltagere",
|
||||
"add": "Tilføj deltager",
|
||||
"organizer": "Arrangør",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Alle begivenheder",
|
||||
"cancel": "Annuller",
|
||||
"save": "Gem",
|
||||
"delete": "Slet"
|
||||
"delete": "Slet",
|
||||
"rsvp_title": "Svar på tilbagevendende begivenhed",
|
||||
"rsvp_description": "Dette er en tilbagevendende begivenhed. Hvilke begivenheder gælder dit svar for?",
|
||||
"respond": "Svar"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Påmindelse",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Vis begivenhedstider i månedskalendervisningen",
|
||||
"show_week_numbers": "Vis ugenumre",
|
||||
"show_week_numbers_desc": "Vis ugenumre i minikalenderen",
|
||||
"calendar_free_scroll": "Fri rulning",
|
||||
"calendar_free_scroll_desc": "Rul fortløbende gennem måneder, uger og dage i stedet for én periode ad gangen",
|
||||
"enable_tasks": "Aktivér opgaver",
|
||||
"enable_tasks_desc": "Vis en opgavevisning i kalenderen til styring af gøremål",
|
||||
"show_tasks_on_calendar": "Vis opgaver på kalender",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "1 sekunds forsinkelse",
|
||||
"hover_preview_delay_2s": "2 sekunders forsinkelse",
|
||||
"hover_preview_off": "Deaktiveret",
|
||||
"organizer_identity": "Organisér invitationer som",
|
||||
"organizer_identity_desc": "Kalenderadressen, der bruges som arrangør, når du inviterer folk til begivenheder.",
|
||||
"organizer_identity_failed": "Kunne ikke ændre arrangøradressen",
|
||||
"show_birthday_calendar": "Kontaktfødselsdagskalender",
|
||||
"show_birthday_calendar_desc": "Vis en virtuel kalender med fødselsdage fra dine kontakter"
|
||||
},
|
||||
@@ -2973,6 +3043,9 @@
|
||||
"alert_now": "Starter nu",
|
||||
"alert_in_minutes": "Om {count} min",
|
||||
"invitation_sent": "Invitationer sendt",
|
||||
"invitations_denied_title": "Invitationerne kan ikke sendes",
|
||||
"invitations_denied": "Serveren afviste at sende invitationerne: {reason}. Gem begivenheden uden at sende dem?",
|
||||
"save_without_invitations": "Gem uden invitationer",
|
||||
"rsvp_updated": "Svar opdateret",
|
||||
"rsvp_error": "Kunne ikke opdatere svar",
|
||||
"event_duplicated": "Begivenhed duplikeret",
|
||||
@@ -3001,7 +3074,7 @@
|
||||
"supported_formats": "Understøtter iCalendar (.ics) filer",
|
||||
"url_description": "Indtast URL'en til et eksternt iCalendar (.ics)-feed for at importere begivenheder.",
|
||||
"url_placeholder": "https://eksempel.dk/kalender.ics",
|
||||
"url_hint": "Understøtter CalDAV og iCalendar (.ics) URL'er",
|
||||
"url_hint": "Understøtter iCalendar (.ics) URL'er",
|
||||
"fetch": "Hent",
|
||||
"invalid_url": "Indtast en gyldig URL",
|
||||
"url_fetch_failed": "Kunne ikke hente kalender fra URL",
|
||||
@@ -3149,6 +3222,25 @@
|
||||
},
|
||||
"nav_open_menu": "Åbn menu"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Nogen",
|
||||
"invited": "{name} har inviteret dig til \"{title}\"",
|
||||
"updated": "{name} har opdateret \"{title}\"",
|
||||
"cancelled": "{name} har aflyst \"{title}\"",
|
||||
"untitled": "Unavngivet begivenhed"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Nogen",
|
||||
"shared": "{name} har delt {kind} \"{object}\" med dig",
|
||||
"changed": "{name} har ændret din adgang til {kind} \"{object}\"",
|
||||
"revoked": "{name} har fjernet din adgang til {kind} \"{object}\"",
|
||||
"object": {
|
||||
"folder": "mappen",
|
||||
"calendar": "kalenderen",
|
||||
"address_book": "adressebogen",
|
||||
"files": "filmappen"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Del \"{name}\"",
|
||||
"description": "Giv adgang til andre brugere eller grupper på denne server. Ændringer træder i kraft øjeblikkeligt.",
|
||||
@@ -3187,6 +3279,8 @@
|
||||
"has_attachment": "Vedhæftninger",
|
||||
"date_after": "Efter",
|
||||
"date_before": "Før",
|
||||
"size_min": "Større end (KB)",
|
||||
"size_max": "Mindre end (KB)",
|
||||
"starred": "Stjernemarkerede",
|
||||
"unread": "Ulæst",
|
||||
"read": "Læst",
|
||||
@@ -3228,6 +3322,10 @@
|
||||
"hint": "Klik på en e-mail til venstre for at komme i gang, eller tag rundvisningen nedenfor."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Rediger dokument",
|
||||
"office_loading": "Åbner dokumenteditor…",
|
||||
"office_error": "Dokumenteditoren kunne ikke åbnes.",
|
||||
"office_close": "Luk editor",
|
||||
"open_in_new_tab": "Åbn i ny fane",
|
||||
"title": "Filer",
|
||||
"search_placeholder": "Søg efter filer...",
|
||||
@@ -3240,6 +3338,8 @@
|
||||
"rename": "Omdøb",
|
||||
"rename_title": "Omdøb",
|
||||
"new_name": "Nyt navn",
|
||||
"name_forbidden_chars": "Serveren tillader ikke disse tegn i navne: {chars}",
|
||||
"name_reserved": "Dette navn er reserveret på serveren.",
|
||||
"delete": "Slet",
|
||||
"delete_confirm_title": "Slet ressource",
|
||||
"delete_confirm_message": "Er du sikker på, at du vil slette \"{name}\"? Dette kan ikke fortrydes.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Kopier link til begivenhed",
|
||||
"copied": "Link kopieret",
|
||||
"copy_failed": "Kunne ikke kopiere linket"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Fuldfører godkendelse…",
|
||||
"completing_message": "Vent venligst, mens forbindelsen oprettes.",
|
||||
"received_title": "Godkendelse modtaget",
|
||||
"received_message": "Du kan vende tilbage til den fane, hvor du startede forbindelsen.",
|
||||
"failed_title": "Godkendelse mislykkedes",
|
||||
"denied": "Godkendelse blev nægtet.",
|
||||
"failed_detail": "Godkendelse mislykkedes: {error}",
|
||||
"missing_params": "Autorisationskode eller tilstand mangler.",
|
||||
"delivery_failed": "Resultatet af godkendelsen kunne ikke leveres til app-fanen. Luk dette vindue og prøv igen."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Søg i alt",
|
||||
"placeholder": "Søg i mail, kontakter, kalender, filer...",
|
||||
"scope_label": "Søg i",
|
||||
"account_label": "Konto",
|
||||
"scope_all": "Alt",
|
||||
"scope_mail": "Mail",
|
||||
"scope_contacts": "Kontakter",
|
||||
"scope_calendar": "Kalender",
|
||||
"scope_files": "Filer",
|
||||
"all_accounts": "Alle konti",
|
||||
"show_all": "Vis alle resultater",
|
||||
"no_results": "Ingen resultater for “{query}”",
|
||||
"server_hint": "Hele ord søger på serveren; dele af ord matcher allerede indlæste elementer",
|
||||
"searching": "Søger...",
|
||||
"account_error": "Kunne ikke søge i {account}",
|
||||
"recent_searches": "Seneste søgninger",
|
||||
"recurring": "Tilbagevendende",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Søg i mail, kontakter, begivenheder og filer på alle konti. Prøv from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Fladt efter dato",
|
||||
"grouped": "Grupperet",
|
||||
"load_more": "Indlæs flere",
|
||||
"preview_empty": "Vælg et resultat for at se det",
|
||||
"open_full": "Åbn",
|
||||
"view_label": "Visning"
|
||||
}
|
||||
}
|
||||
|
||||
+212
-72
@@ -142,8 +142,8 @@
|
||||
"unified_all_unread": "Alle ungelesenen",
|
||||
"unified_all_starred": "Alle markierten",
|
||||
"unified_all_mail": "Alle Mails",
|
||||
"remove_account": "Remove account",
|
||||
"remove_account_confirm": "Remove {account} from this device? You can add it back later."
|
||||
"remove_account": "Konto entfernen",
|
||||
"remove_account_confirm": "{account} von diesem Gerät entfernen? Sie können das Konto später wieder hinzufügen."
|
||||
},
|
||||
"protocol_handlers": {
|
||||
"title": "Standard-Apps",
|
||||
@@ -152,7 +152,7 @@
|
||||
"mailto_label": "E-Mail-Links",
|
||||
"mailto_description": "Öffnet mailto:-Links in Bulwark mit vorausgefülltem Editor.",
|
||||
"protocol_open_mode_label": "Beim Öffnen von Protokoll-Links",
|
||||
"protocol_open_mode_description": "Wähle, ob Bulwark mailto:- und webcal:-Links immer in einem neuen Tab öffnet oder eine offene Sitzung wiederverwendet. Für die aktive Sitzung benötigt Bulwark Benachrichtigungen, damit du das Fenster per Klick in den Vordergrund holen kannst, falls der Browser den Fokus blockiert.",
|
||||
"protocol_open_mode_description": "Legen Sie fest, ob Bulwark mailto:- und webcal:-Links immer in einem neuen Tab öffnet oder eine offene Sitzung wiederverwendet. Für die aktive Sitzung benötigt Bulwark Benachrichtigungen, damit Sie das Fenster per Klick in den Vordergrund holen können, falls der Browser den Fokus blockiert.",
|
||||
"protocol_open_mode_active_session": "Wenn möglich in aktiver Sitzung öffnen",
|
||||
"protocol_open_mode_new_tab": "Immer neuen Tab öffnen",
|
||||
"focus_notification_title": "Bulwark öffnen",
|
||||
@@ -168,8 +168,8 @@
|
||||
"opening_webcal": "Kalender wird geöffnet...",
|
||||
"browser_note": "Ihr Browser oder Betriebssystem kann eine Bestätigung verlangen. Eventuell muss Bulwark installiert sein, bevor es als Standard-App ausgewählt werden kann.",
|
||||
"select_account_title": "Account auswählen",
|
||||
"select_mailto_account": "Wähle aus, mit welchem Account dieser E-Mail-Link geöffnet werden soll.",
|
||||
"select_webcal_account": "Wähle aus, mit welchem Account dieser Kalender-Link geöffnet werden soll.",
|
||||
"select_mailto_account": "Wählen Sie aus, mit welchem Account dieser E-Mail-Link geöffnet werden soll.",
|
||||
"select_webcal_account": "Wählen Sie aus, mit welchem Account dieser Kalender-Link geöffnet werden soll.",
|
||||
"select_account_note": "Diese Auswahl gilt nur für diesen Protokoll-Link.",
|
||||
"detail_to": "An",
|
||||
"detail_subject": "Betreff",
|
||||
@@ -225,6 +225,10 @@
|
||||
"and_others": "und {count} weitere",
|
||||
"draft": "Entwurf",
|
||||
"starred": "Mit Stern",
|
||||
"pinned": "Angeheftet",
|
||||
"replied": "Beantwortet",
|
||||
"forwarded": "Weitergeleitet",
|
||||
"has_attachment": "Mit Anhang",
|
||||
"conversations_count": "{count} von {total} Unterhaltungen",
|
||||
"conversations_count_plus": "{count}+ Unterhaltungen",
|
||||
"conversations_count_simple": "{count} Unterhaltungen",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Weiterleiten",
|
||||
"delete": "Löschen",
|
||||
"archive": "Archivieren",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Stern hinzufügen",
|
||||
"unstar": "Stern entfernen",
|
||||
"mark_unread": "Als ungelesen markieren",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Verschieben",
|
||||
"print": "Drucken",
|
||||
"view_source": "Quelltext anzeigen",
|
||||
|
||||
"fullscreen": "Vollbild",
|
||||
|
||||
"exit_fullscreen": "Vollbild beenden",
|
||||
"export_email": "Als .eml exportieren",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Als Anhang weiterleiten",
|
||||
"import_email": ".eml oder .zip importieren",
|
||||
"keyboard_shortcuts": "Tastaturkürzel (?)",
|
||||
"email_source": "E-Mail-Quelltext",
|
||||
@@ -509,8 +512,8 @@
|
||||
"review_request": "Anfrage prüfen",
|
||||
"view_in_calendar": "Im Kalender anzeigen",
|
||||
"select_calendar": "Kalender auswählen",
|
||||
"already_in_calendar": "Bereits in deinem Kalender",
|
||||
"request_info": "Du wurdest zu diesem Termin eingeladen. Antworte, um dem Organisator deine Verfügbarkeit mitzuteilen.",
|
||||
"already_in_calendar": "Bereits in Ihrem Kalender",
|
||||
"request_info": "Sie wurden zu diesem Termin eingeladen. Antworten Sie, um dem Organisator Ihre Verfügbarkeit mitzuteilen.",
|
||||
"cancel_info": "Der Organisator hat diesen Termin abgesagt.",
|
||||
"event_updated": "Aktualisierung #{sequence}",
|
||||
"event_status_tentative": "Vorläufig",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Lesebestätigung angefordert (klicken zum Deaktivieren)",
|
||||
"read_receipt_off": "Lesebestätigung anfordern",
|
||||
"dsn_on": "Zustellbenachrichtigung angefordert (zum Deaktivieren klicken)",
|
||||
"dsn_off": "Zustellbenachrichtigung anfordern",
|
||||
"format_plain_text": "Zu Nur-Text wechseln",
|
||||
"format_rich_text": "Zu formatiertem Text (HTML) wechseln",
|
||||
"require_tls_on": "Verschlüsselte Zustellung erforderlich (zum Deaktivieren klicken)",
|
||||
"require_tls_off": "Verschlüsselte Zustellung verlangen (TLS)",
|
||||
"new_message": "Neue Nachricht",
|
||||
"signature_edit_hint": "Doppelklick, um die Signatur zu bearbeiten",
|
||||
"reply": "Antworten",
|
||||
@@ -597,8 +606,8 @@
|
||||
"send": "Senden",
|
||||
"cancel": "Abbrechen",
|
||||
"attach": "Anhängen",
|
||||
"attach_photos": "Photos & Videos",
|
||||
"attach_files": "Files",
|
||||
"attach_photos": "Fotos & Videos",
|
||||
"attach_files": "Dateien",
|
||||
"discard": "Verwerfen",
|
||||
"discard_draft_title": "Entwurf verwerfen?",
|
||||
"discard_draft_confirm": "Sie haben ungespeicherte Änderungen. Möchten Sie diesen Entwurf verwerfen?",
|
||||
@@ -631,11 +640,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Überschreiben",
|
||||
"toggle_on": "Überschreibung aufheben",
|
||||
"toggle_tooltip": "Bearbeiten Sie Absendername und -adresse frei. Die E-Mail wird weiterhin über Ihre Identität gesendet - nur die sichtbare Absenderkopfzeile ändert sich.",
|
||||
"toggle_tooltip": "Bearbeiten Sie Absendername und -adresse frei. Die E-Mail wird weiterhin über Ihre Identität gesendet.",
|
||||
"name_label": "Absendername",
|
||||
"name_placeholder": "Name",
|
||||
"email_label": "Absender-E-Mail-Adresse",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "Akzeptiert Ihr Server diese Adresse nicht als Umschlag-Absender, wird dort {identity} verwendet, und Empfänger können sie in der Kopfzeile Return-Path sehen."
|
||||
},
|
||||
"use_template": "Vorlage",
|
||||
"save_as_template": "Als Vorlage speichern",
|
||||
@@ -648,6 +658,8 @@
|
||||
"upload_progress": "Hochladen {uploaded} / {total}",
|
||||
"upload_cancel": "Hochladen abbrechen",
|
||||
"upload_failed": "Hochladen von {filename} fehlgeschlagen",
|
||||
"attachment_too_large": "\"{name}\" ist größer, als der Server erlaubt ({max} pro Datei)",
|
||||
"attachments_total_too_large": "Die Anhänge würden das Limit des Servers von {max} pro Nachricht überschreiten",
|
||||
"send_failed": "E-Mail konnte nicht gesendet werden",
|
||||
"smime_sign_on": "S/MIME-Signatur aktiviert",
|
||||
"smime_sign_off": "S/MIME-Signatur deaktiviert",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Spalte löschen",
|
||||
"toggle_header_row": "Kopfzeile umschalten",
|
||||
"delete_table": "Tabelle löschen",
|
||||
"pick_size": "Größe wählen"
|
||||
"pick_size": "Größe wählen",
|
||||
"font_size": "Schriftgröße",
|
||||
"font_size_default": "Standard",
|
||||
"background_color": "Hintergrundfarbe",
|
||||
"remove_background_color": "Hintergrundfarbe entfernen"
|
||||
},
|
||||
"send_filing_warning": "Gesendet - aber das Aufräumen danach schlug fehl, evtl. bleibt ein alter Entwurf sichtbar."
|
||||
},
|
||||
@@ -753,7 +769,7 @@
|
||||
"unknown": "Unbekannt",
|
||||
"app_title": "Webmail",
|
||||
"reconnecting": "Verbindung verloren. Verbindung wird wiederhergestellt…",
|
||||
"rate_limited_title": "Die Serverauthentifizierung ist vorubergehend begrenzt.",
|
||||
"rate_limited_title": "Die Serverauthentifizierung ist vorübergehend begrenzt.",
|
||||
"rate_limited_detail": "Bulwark hat Hintergrundanfragen pausiert, um eine Sperre zu vermeiden. Neuer Versuch in {seconds}s.",
|
||||
"rate_limited_action_title": "Anfrage pausiert, um eine Sperre zu vermeiden.",
|
||||
"rate_limited_action_detail": "Bulwark wartet, bis die Serverabklingzeit endet, bevor weitere authentifizierte Anfragen gesendet werden. Versuchen Sie es in {seconds}s erneut.",
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "E-Mail erfolgreich gesendet",
|
||||
"email_deleted": "E-Mail gelöscht",
|
||||
"email_archived": "E-Mail archiviert",
|
||||
"emails_deleted": "{count, plural, one {# E-Mail gelöscht} other {# E-Mails gelöscht}}",
|
||||
"emails_archived": "{count, plural, one {# E-Mail archiviert} other {# E-Mails archiviert}}",
|
||||
"error_archiving": "E-Mail konnte nicht archiviert werden",
|
||||
"error_updating": "E-Mail konnte nicht aktualisiert werden",
|
||||
"email_starred": "Stern hinzugefügt",
|
||||
"email_unstarred": "Stern entfernt",
|
||||
"email_marked_read": "E-Mail als gelesen markiert",
|
||||
@@ -1037,26 +1057,26 @@
|
||||
}
|
||||
},
|
||||
"all_mail": {
|
||||
"folders_label": "Folders in All Mail",
|
||||
"folders_description": "Choose which folders are merged into the All Mail view.",
|
||||
"folders_label": "Ordner in „Alle Mails“",
|
||||
"folders_description": "Wählen Sie aus, welche Ordner in der Ansicht „Alle Mails“ zusammengeführt werden.",
|
||||
"account_hint": "Gilt für {account}.",
|
||||
"no_folders": "No folders available."
|
||||
"no_folders": "Keine Ordner verfügbar."
|
||||
},
|
||||
"colorful_sidebar_icons": {
|
||||
"label": "Farbige Seitenleistensymbole",
|
||||
"description": "Ordner- und Tag-Symbole nach Typ einfärben (blauer Posteingang, roter Spam, grüner Gesendet usw.). Für eine monochrome Seitenleiste deaktivieren."
|
||||
},
|
||||
"tint_list_rows": {
|
||||
"label": "Tint List Rows by Tag Color",
|
||||
"description": "Shade each message row with its first tag color. Disable to keep rows plain; tag dots and chips still show the color."
|
||||
"label": "Nachrichtenzeilen mit Tag-Farbe einfärben",
|
||||
"description": "Die einzelnen Nachrichtenzeilen mit der Farbe ihres ersten Tags einfärben. Deaktivieren Sie diese Option, um die Zeilen neutral zu halten; Tag-Punkte und -Markierungen zeigen die Farbe weiterhin an."
|
||||
},
|
||||
"show_folder_total_count": {
|
||||
"label": "Gesamtzahl der Nachrichten anzeigen",
|
||||
"description": "Zeigt neben Ordnern und Tags die Gesamtzahl der Nachrichten zusätzlich zur Anzahl ungelesener Nachrichten an. Deaktivieren, um nur ungelesene Nachrichten anzuzeigen."
|
||||
},
|
||||
"favicon_unread_badge": {
|
||||
"label": "Unread Count on Tab Icon",
|
||||
"description": "Show the inbox unread count as a badge on the browser tab icon, so new mail is visible when the tab is not focused. Disable to keep the icon unbadged."
|
||||
"label": "Anzahl ungelesener Nachrichten am Tab-Symbol",
|
||||
"description": "Die Anzahl ungelesener Nachrichten im Posteingang als Abzeichen am Browser-Tab-Symbol anzeigen, damit neue E-Mails auch bei nicht fokussiertem Tab sichtbar sind. Deaktivieren Sie diese Option, um das Symbol ohne Abzeichen anzuzeigen."
|
||||
},
|
||||
"pro_interface": {
|
||||
"label": "Pro-Oberfläche (experimentell)",
|
||||
@@ -1066,7 +1086,7 @@
|
||||
},
|
||||
"cross_unread": {
|
||||
"label": "Alle Konten: Ungelesen",
|
||||
"description": "Zeigt in der Sektion „All accounts“ einen Eintrag mit ungelesenen Mails über alle Konten, über alle Ordner außer Junk, Gesendet, Archiv, Papierkorb und Entwürfe."
|
||||
"description": "Zeigt in der Sektion „All accounts“ einen Eintrag mit ungelesenen Mails über alle Konten und Ordner außer Junk, Gesendet, Archiv, Papierkorb und Entwürfe."
|
||||
},
|
||||
"cross_starred": {
|
||||
"label": "Alle Konten: Markiert",
|
||||
@@ -1143,7 +1163,9 @@
|
||||
"enabled": "E-Mail-Benachrichtigungen",
|
||||
"enabled_desc": "Benachrichtigungen anzeigen, wenn neue E-Mails eintreffen",
|
||||
"sound": "Benachrichtigungston",
|
||||
"sound_desc": "Einen Ton abspielen, wenn neue E-Mails eintreffen"
|
||||
"sound_desc": "Einen Ton abspielen, wenn neue E-Mails eintreffen",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Kalender-Benachrichtigungen",
|
||||
@@ -1172,6 +1194,7 @@
|
||||
"status_busy": "Wird verarbeitet…",
|
||||
"status_inactive": "Auf diesem Gerät nicht aktiviert",
|
||||
"status_unsupported": "Dieser Browser unterstützt Web Push nicht",
|
||||
"status_lite": "Web Push ist in Bulwark Lite nicht verfügbar; es benötigt den Bulwark-Server.",
|
||||
"title": "Hintergrundbenachrichtigungen",
|
||||
"devices_title": "Registrierte Geräte",
|
||||
"devices_desc": "Alle Geräte, die Hintergrundbenachrichtigungen für dieses Konto erhalten. Wird eines widerrufen, empfängt es sofort keine mehr.",
|
||||
@@ -1211,12 +1234,12 @@
|
||||
"preview_older": "Älter:"
|
||||
},
|
||||
"date_locale": {
|
||||
"label": "Date format region",
|
||||
"description": "How numeric dates are ordered (day, month, year)",
|
||||
"auto": "Automatic (match language)",
|
||||
"label": "Region für Datumsformat",
|
||||
"description": "Legt fest, in welcher Reihenfolge numerische Datumsangaben angezeigt werden (Tag, Monat, Jahr).",
|
||||
"auto": "Automatisch (passend zur Sprache)",
|
||||
"iso": "ISO 8601 (YYYY-MM-DD)",
|
||||
"dmy": "Day/Month/Year",
|
||||
"mdy": "Month/Day/Year"
|
||||
"dmy": "Tag/Monat/Jahr",
|
||||
"mdy": "Monat/Tag/Jahr"
|
||||
},
|
||||
"time_format": {
|
||||
"label": "Zeitformat",
|
||||
@@ -1231,13 +1254,17 @@
|
||||
"monday": "Montag"
|
||||
},
|
||||
"time_zone": {
|
||||
"label": "Time zone",
|
||||
"description": "Show email and calendar times in this time zone instead of the one reported by your browser",
|
||||
"auto": "Automatic ({zone})",
|
||||
"preview_now": "Now:"
|
||||
"label": "Zeitzone",
|
||||
"description": "E-Mail- und Kalenderzeiten in dieser Zeitzone statt in der von Ihrem Browser gemeldeten Zeitzone anzeigen",
|
||||
"auto": "Automatisch ({zone})",
|
||||
"preview_now": "Aktuelle Zeit:"
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Suche beim Ordnerwechsel löschen",
|
||||
"description": "Ein Klick auf einen Ordner (Posteingang, Gesendet …) löscht die aktive Suche und zeigt den Ordner. Ist die Option aus, bleibt die Suche bestehen und wird im neuen Ordner erneut ausgeführt."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Lesebestätigung standardmäßig anfordern",
|
||||
"description": "Beim Verfassen einer neuen Nachricht die Anforderung einer Lesebestätigung vorab aktivieren."
|
||||
@@ -1321,6 +1348,12 @@
|
||||
"label": "Antwortadresse automatisch wählen",
|
||||
"description": "Beim Antworten die Absenderadresse automatisch auf die Identität umstellen, die die ursprüngliche Nachricht erhalten hat"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Abgleich der Empfangsadresse",
|
||||
"description": "Welche Empfangsadressen als eigene gelten. „Nur exakte Adresse“ wählt ausschließlich eine Ihrer eingerichteten Identitäten. „Gleiche Domain“ behandelt zusätzlich jede andere Adresse auf einer Ihrer Identitäts-Domains als Catch-all-Alias und ersetzt den Absender-Header damit. Wählen Sie die exakte Adresse, wenn solche Adressen Verteilerlisten und keine Aliasse sind.",
|
||||
"exact": "Nur exakte Adresse",
|
||||
"domain": "Jede Adresse auf meinen Domains"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Signaturposition",
|
||||
"description": "Wo Ihre Signatur in Antworten und Weiterleitungen eingefügt wird. Über dem zitierten Text liest sie sich natürlich als Abschluss der Antwort; darunter bleibt die ursprüngliche Nachricht zusammenhängend.",
|
||||
@@ -1387,7 +1420,7 @@
|
||||
"close": "Schließen",
|
||||
"invalid_email": "Bitte geben Sie eine gültige E-Mail-Adresse ein",
|
||||
"already_added": "Dieser Absender ist bereits vertrauenswürdig",
|
||||
"save_error": "Speichern fehlgeschlagen - prüfe das Kontakte-Debug-Log für Details",
|
||||
"save_error": "Speichern fehlgeschlagen - prüfen Sie das Kontakte-Debug-Log für Details",
|
||||
"use_address_book_label": "Mit Adressbuch synchronisieren",
|
||||
"use_address_book_description": "Vertrauenswürdige Absender in einem eigenen \"Vertrauenswürdige Absender\"-Adressbuch speichern, damit sie auf allen Geräten synchronisiert werden"
|
||||
},
|
||||
@@ -1746,7 +1779,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "Das Enddatum muss nach dem Startdatum liegen",
|
||||
"start_in_past": "Das Startdatum liegt in der Vergangenheit",
|
||||
"empty_body": "Der Nachrichtentext ist leer - Empfänger erhalten eine leere Antwort"
|
||||
"empty_body": "Der Nachrichtentext ist leer - Empfänger erhalten eine leere Antwort",
|
||||
"subject_too_long": "Der Betreff ist für den Server zu lang (höchstens {max} Byte).",
|
||||
"body_too_long": "Die Nachricht ist für den Server zu lang (höchstens {max} Byte, inklusive Formatierung)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1763,6 +1798,8 @@
|
||||
"role_trash": "Papierkorb",
|
||||
"role_junk": "Spam / Junk",
|
||||
"role_archive": "Archiv",
|
||||
"role_scheduled": "Geplant",
|
||||
"role_snoozed": "Zurückgestellt",
|
||||
"role_none": "Keine",
|
||||
"create_folder": "Ordner erstellen",
|
||||
"create_subfolder": "Unterordner erstellen",
|
||||
@@ -1835,9 +1872,9 @@
|
||||
"button": "Tastaturkürzel anzeigen"
|
||||
},
|
||||
"refresh_cache": {
|
||||
"label": "Refresh cached data",
|
||||
"description": "Reload contacts, calendars, and folders from the server. Keeps your accounts and sessions — fixes a stale or wrong view without signing out.",
|
||||
"button": "Refresh"
|
||||
"label": "Zwischengespeicherte Daten aktualisieren",
|
||||
"description": "Kontakte, Kalender und Ordner vom Server neu laden. Konten und Sitzungen bleiben erhalten; veraltete oder fehlerhafte Ansichten werden korrigiert, ohne dass Sie sich abmelden müssen.",
|
||||
"button": "Aktualisieren"
|
||||
},
|
||||
"reset_settings": {
|
||||
"label": "Einstellungen zurücksetzen",
|
||||
@@ -1882,7 +1919,9 @@
|
||||
"categories_description": "Kontaktkategorien umbenennen",
|
||||
"no_categories": "Keine Kategorien gefunden",
|
||||
"group_by_letter_description": "Alphabetische Abschnittsüberschriften in der Kontaktliste anzeigen",
|
||||
"group_by_letter_label": "Nach Anfangsbuchstaben gruppieren"
|
||||
"group_by_letter_label": "Nach Anfangsbuchstaben gruppieren",
|
||||
"sort_by_last_name_label": "Nach Nachname sortieren",
|
||||
"sort_by_last_name_description": "Kontaktliste nach Nachnamen ordnen, damit Familienmitglieder zusammenstehen"
|
||||
},
|
||||
"downloads": {
|
||||
"title": "Downloads",
|
||||
@@ -1963,6 +2002,7 @@
|
||||
"actions": "Aktionen",
|
||||
"add_action": "Aktion hinzufügen",
|
||||
"stop_processing": "Verarbeitung nachfolgender Regeln stoppen",
|
||||
"include_spam": "Auch als Spam markierte Nachrichten verschieben",
|
||||
"attachment_type_placeholder": "z.B. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Wert (mehrere mit Komma trennen)",
|
||||
"condition_fields": {
|
||||
@@ -2004,6 +2044,8 @@
|
||||
"copy_to_folder": "Ordner auswählen",
|
||||
"forward_to": "An E-Mail-Adresse weiterleiten",
|
||||
"forward_placeholder": "email@beispiel.de",
|
||||
"forward_keep_copy": "Kopie behalten",
|
||||
"forward_limit": "Weiterleitungen pro Nachricht auf diesem Server: höchstens {count}. Weitere werden übersprungen.",
|
||||
"reject_message": "Ablehnungsnachricht",
|
||||
"reject_placeholder": "Ihre E-Mail wurde abgelehnt",
|
||||
"label_name": "Label-Name",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Neuer Unterordner...",
|
||||
"new_folder": "Neuer Ordner...",
|
||||
"rename": "Umbenennen...",
|
||||
"share": "Freigeben...",
|
||||
"import_email": ".eml oder .zip importieren...",
|
||||
"empty_folder": "Ordner leeren",
|
||||
"empty_folder_generic": "Ordner leeren",
|
||||
@@ -2236,7 +2279,10 @@
|
||||
"toast_error_rename": "Ordner konnte nicht umbenannt werden",
|
||||
"toast_error_delete": "Ordner konnte nicht gelöscht werden",
|
||||
"toast_error_delete_has_children": "Ordner enthält Unterordner. Entferne diese zuerst.",
|
||||
"toast_error_delete_has_email": "Ordner ist nicht leer. Leere ihn zuerst."
|
||||
"toast_error_delete_has_email": "Ordner ist nicht leer. Leere ihn zuerst.",
|
||||
"delete_with_contents_title": "Ordner ist nicht leer",
|
||||
"delete_with_contents_message": "Der Ordner „{name}“ enthält noch {count, plural, one {1 Nachricht} other {# Nachrichten}}. Ordner samt allen Nachrichten löschen? Das kann nicht rückgängig gemacht werden.",
|
||||
"delete_with_contents_confirm": "Ordner und Nachrichten löschen"
|
||||
},
|
||||
"shortcuts": {
|
||||
"title": "Tastaturkürzel",
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Suche fokussieren",
|
||||
"help": "Tastaturkürzel anzeigen",
|
||||
"refresh": "E-Mails aktualisieren",
|
||||
"select_all": "Alle auswählen"
|
||||
"select_all": "Alle auswählen",
|
||||
"global_search": "Alles durchsuchen"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Unterhaltung erweitern/einklappen"
|
||||
@@ -2496,32 +2543,32 @@
|
||||
"cert_already_imported": "Zertifikat bereits importiert",
|
||||
"cert_imported": "Zertifikat importiert",
|
||||
"cert_import_failed": "Import des Zertifikats fehlgeschlagen",
|
||||
"section_contact": "Contact details",
|
||||
"section_work": "Work",
|
||||
"section_personal": "Personal",
|
||||
"email_default_label": "Email",
|
||||
"phone_default_label": "Phone",
|
||||
"address_default_label": "Address",
|
||||
"section_contact": "Kontaktdaten",
|
||||
"section_work": "Berufliches",
|
||||
"section_personal": "Persönliches",
|
||||
"email_default_label": "E-Mail",
|
||||
"phone_default_label": "Telefon",
|
||||
"address_default_label": "Adresse",
|
||||
"online_service_default_label": "Online",
|
||||
"organization_label": "Organization",
|
||||
"title_label": "Title",
|
||||
"role_label": "Role",
|
||||
"language_label": "Language",
|
||||
"related_default_label": "Related",
|
||||
"organization_label": "Organisation",
|
||||
"title_label": "Titel",
|
||||
"role_label": "Rolle",
|
||||
"language_label": "Sprache",
|
||||
"related_default_label": "Verknüpft",
|
||||
"more_actions": "Weitere Aktionen",
|
||||
"age_years": "{count, plural, one {1 Jahr alt} other {# Jahre alt}}",
|
||||
"years_since": "{count, plural, one {1 Jahr} other {# Jahre}}"
|
||||
},
|
||||
"activity": {
|
||||
"recent_emails": "Recent Emails",
|
||||
"upcoming_events": "Upcoming Events",
|
||||
"no_emails": "No recent emails",
|
||||
"no_events": "No upcoming events",
|
||||
"no_subject": "(No subject)",
|
||||
"no_title": "(No title)",
|
||||
"load_failed": "Failed to load",
|
||||
"unknown_sender": "Unknown sender",
|
||||
"all_day": "All day"
|
||||
"recent_emails": "Letzte E-Mails",
|
||||
"upcoming_events": "Bevorstehende Termine",
|
||||
"no_emails": "Keine aktuellen E-Mails",
|
||||
"no_events": "Keine bevorstehenden Termine",
|
||||
"no_subject": "(Kein Betreff)",
|
||||
"no_title": "(Kein Titel)",
|
||||
"load_failed": "Laden fehlgeschlagen",
|
||||
"unknown_sender": "Unbekannter Absender",
|
||||
"all_day": "Ganztägig"
|
||||
},
|
||||
"form": {
|
||||
"create_title": "Neuer Kontakt",
|
||||
@@ -2642,11 +2689,11 @@
|
||||
"search_members": "Kontakte zum Hinzufügen suchen...",
|
||||
"no_members": "Keine Mitglieder in dieser Gruppe",
|
||||
"member_count": "{count, plural, =0 {Keine Mitglieder} one {1 Mitglied} other {# Mitglieder}}",
|
||||
"send_email": "Send email to group",
|
||||
"send_email_to": "To",
|
||||
"send_email": "E-Mail an Gruppe senden",
|
||||
"send_email_to": "An",
|
||||
"send_email_cc": "Cc",
|
||||
"send_email_bcc": "Bcc",
|
||||
"no_member_emails": "This group has no members with an email address."
|
||||
"no_member_emails": "Diese Gruppe hat keine Mitglieder mit einer E-Mail-Adresse."
|
||||
},
|
||||
"import": {
|
||||
"title": "Kontakte importieren",
|
||||
@@ -2721,7 +2768,10 @@
|
||||
"any_month": "Beliebiger Monat",
|
||||
"has_email": "Mit E-Mail",
|
||||
"has_phone": "Mit Telefon",
|
||||
"has_photo": "Mit Foto"
|
||||
"has_photo": "Mit Foto",
|
||||
"sort_by": "Sortieren nach",
|
||||
"sort_first_name": "Vorname",
|
||||
"sort_last_name": "Nachname"
|
||||
},
|
||||
"open_categories": "Kategorien öffnen"
|
||||
},
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "Duplizieren",
|
||||
"today_header": "Heute",
|
||||
"tomorrow_header": "Morgen",
|
||||
"agenda_show_earlier": "Frühere Termine anzeigen",
|
||||
"agenda_loading": "Wird geladen…",
|
||||
"agenda_range_start": "Termine ab {date}",
|
||||
"agenda_range_end": "Termine bis {date}",
|
||||
"export_ics": "Als .ics exportieren",
|
||||
"copy_title": "Titel kopieren",
|
||||
"copy_link": "Meeting-Link kopieren",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "Farbe"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Verfügbarkeit",
|
||||
"free": "Verfügbar",
|
||||
"busy": "Beschäftigt",
|
||||
"tentative": "Vorläufig",
|
||||
"unknown": "Kein Benutzer auf diesem Server",
|
||||
"checking": "Wird geprüft…"
|
||||
},
|
||||
"title": "Teilnehmer",
|
||||
"add": "Teilnehmer hinzufügen",
|
||||
"organizer": "Organisator",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Alle Ereignisse",
|
||||
"cancel": "Abbrechen",
|
||||
"save": "Speichern",
|
||||
"delete": "Löschen"
|
||||
"delete": "Löschen",
|
||||
"rsvp_title": "Auf wiederkehrendes Ereignis antworten",
|
||||
"rsvp_description": "Dies ist ein wiederkehrendes Ereignis. Für welche Ereignisse gilt Ihre Antwort?",
|
||||
"respond": "Antworten"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Erinnerung",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Ereigniszeiten in der Monatskalenderansicht anzeigen",
|
||||
"show_week_numbers": "Kalenderwochen anzeigen",
|
||||
"show_week_numbers_desc": "Kalenderwochen im Minikalender anzeigen",
|
||||
"calendar_free_scroll": "Freies Scrollen",
|
||||
"calendar_free_scroll_desc": "Fortlaufend durch Monate, Wochen und Tage scrollen statt nur einen Zeitraum auf einmal anzuzeigen",
|
||||
"enable_tasks": "Aufgaben aktivieren",
|
||||
"enable_tasks_desc": "Eine Aufgabenansicht im Kalender zum Verwalten von To-dos anzeigen",
|
||||
"show_tasks_on_calendar": "Aufgaben im Kalender anzeigen",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "1 Sekunde Verzögerung",
|
||||
"hover_preview_delay_2s": "2 Sekunden Verzögerung",
|
||||
"hover_preview_off": "Deaktiviert",
|
||||
"organizer_identity": "Einladungen organisieren als",
|
||||
"organizer_identity_desc": "Die Kalenderadresse, die als Organisator verwendet wird, wenn du Personen zu Terminen einlädst.",
|
||||
"organizer_identity_failed": "Organisatoradresse konnte nicht geändert werden",
|
||||
"show_birthday_calendar": "Geburtstags-Kalender",
|
||||
"show_birthday_calendar_desc": "Zeigt einen virtuellen Kalender mit Geburtstagen aus deinen Kontakten"
|
||||
},
|
||||
@@ -2947,6 +3017,9 @@
|
||||
"alert_now": "Beginnt jetzt",
|
||||
"alert_in_minutes": "In {count} Min.",
|
||||
"invitation_sent": "Einladungen gesendet",
|
||||
"invitations_denied_title": "Einladungen können nicht gesendet werden",
|
||||
"invitations_denied": "Der Server hat das Senden der Einladungen abgelehnt: {reason}. Termin ohne Einladungen speichern?",
|
||||
"save_without_invitations": "Ohne Einladungen speichern",
|
||||
"rsvp_updated": "Antwort aktualisiert",
|
||||
"rsvp_error": "Antwort konnte nicht aktualisiert werden",
|
||||
"event_duplicated": "Termin dupliziert",
|
||||
@@ -2975,7 +3048,7 @@
|
||||
"supported_formats": "iCalendar (.ics) Dateien werden unterstützt",
|
||||
"url_description": "Geben Sie die URL eines externen iCalendar (.ics) Feeds ein, um Termine zu importieren.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "Unterstützt CalDAV und iCalendar (.ics) URLs",
|
||||
"url_hint": "Unterstützt iCalendar (.ics) URLs",
|
||||
"fetch": "Abrufen",
|
||||
"invalid_url": "Bitte geben Sie eine gültige URL ein",
|
||||
"url_fetch_failed": "Kalender konnte nicht von der URL abgerufen werden",
|
||||
@@ -3164,6 +3237,8 @@
|
||||
"has_attachment": "Anhänge",
|
||||
"date_after": "Nach dem",
|
||||
"date_before": "Vor dem",
|
||||
"size_min": "Größer als (KB)",
|
||||
"size_max": "Kleiner als (KB)",
|
||||
"starred": "Markiert",
|
||||
"unread": "Ungelesen",
|
||||
"read": "Gelesen",
|
||||
@@ -3205,6 +3280,10 @@
|
||||
"hint": "Klicken Sie links auf eine E-Mail, um loszulegen, oder starten Sie die Tour."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Dokument bearbeiten",
|
||||
"office_loading": "Dokumenteditor wird geöffnet…",
|
||||
"office_error": "Der Dokumenteditor konnte nicht geöffnet werden.",
|
||||
"office_close": "Editor schließen",
|
||||
"open_in_new_tab": "In neuem Tab öffnen",
|
||||
"title": "Dateien",
|
||||
"search_placeholder": "Dateien suchen...",
|
||||
@@ -3217,6 +3296,8 @@
|
||||
"rename": "Umbenennen",
|
||||
"rename_title": "Umbenennen",
|
||||
"new_name": "Neuer Name",
|
||||
"name_forbidden_chars": "Diese Zeichen sind in Namen auf dem Server nicht erlaubt: {chars}",
|
||||
"name_reserved": "Dieser Name ist auf dem Server reserviert.",
|
||||
"delete": "Löschen",
|
||||
"delete_confirm_title": "Ressource löschen",
|
||||
"delete_confirm_message": "Möchten Sie \"{name}\" wirklich löschen? Dies kann nicht rückgängig gemacht werden.",
|
||||
@@ -3268,10 +3349,10 @@
|
||||
"duplicate": "Duplizieren",
|
||||
"duplicate_success": "Erfolgreich dupliziert",
|
||||
"duplicate_error": "Duplizieren fehlgeschlagen",
|
||||
"share": "Share",
|
||||
"shared": "Shared",
|
||||
"shared_with_me": "Shared with me",
|
||||
"shared_by": "Shared by {name}",
|
||||
"share": "Teilen",
|
||||
"shared": "Geteilt",
|
||||
"shared_with_me": "Mit mir geteilt",
|
||||
"shared_by": "Von {name} geteilt",
|
||||
"create_file_success": "Datei erstellt",
|
||||
"create_file_error": "Datei konnte nicht erstellt werden",
|
||||
"favorites": "Favoriten",
|
||||
@@ -3451,6 +3532,25 @@
|
||||
"unified_mailbox": {
|
||||
"search_unavailable": "Die Suche ist in der vereinheitlichten Ansicht nicht verfügbar"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Jemand",
|
||||
"invited": "{name} hat dich zu „{title}“ eingeladen",
|
||||
"updated": "{name} hat „{title}“ aktualisiert",
|
||||
"cancelled": "{name} hat „{title}“ abgesagt",
|
||||
"untitled": "Unbenannter Termin"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Jemand",
|
||||
"shared": "{name} hat {kind} „{object}“ mit dir geteilt",
|
||||
"changed": "{name} hat deinen Zugriff auf {kind} „{object}“ geändert",
|
||||
"revoked": "{name} hat deinen Zugriff auf {kind} „{object}“ entfernt",
|
||||
"object": {
|
||||
"folder": "den Ordner",
|
||||
"calendar": "den Kalender",
|
||||
"address_book": "das Adressbuch",
|
||||
"files": "den Dateiordner"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "„{name}\" freigeben",
|
||||
"description": "Anderen Benutzern oder Gruppen auf diesem Server Zugriff gewähren. Änderungen werden sofort wirksam.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Link zum Termin kopieren",
|
||||
"copied": "Link kopiert",
|
||||
"copy_failed": "Link konnte nicht kopiert werden"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Autorisierung wird abgeschlossen…",
|
||||
"completing_message": "Bitte warten, während die Verbindung hergestellt wird.",
|
||||
"received_title": "Autorisierung empfangen",
|
||||
"received_message": "Sie können zu dem Tab zurückkehren, in dem Sie die Verbindung gestartet haben.",
|
||||
"failed_title": "Autorisierung fehlgeschlagen",
|
||||
"denied": "Autorisierung wurde verweigert.",
|
||||
"failed_detail": "Autorisierung fehlgeschlagen: {error}",
|
||||
"missing_params": "Autorisierungscode oder State fehlt.",
|
||||
"delivery_failed": "Das Autorisierungsergebnis konnte nicht an den App-Tab übermittelt werden. Bitte schließen Sie dieses Fenster und versuchen Sie es erneut."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Alles durchsuchen",
|
||||
"placeholder": "Mail, Kontakte, Kalender, Dateien durchsuchen...",
|
||||
"scope_label": "Suchen in",
|
||||
"account_label": "Konto",
|
||||
"scope_all": "Alle",
|
||||
"scope_mail": "E-Mail",
|
||||
"scope_contacts": "Kontakte",
|
||||
"scope_calendar": "Kalender",
|
||||
"scope_files": "Dateien",
|
||||
"all_accounts": "Alle Konten",
|
||||
"show_all": "Alle Ergebnisse anzeigen",
|
||||
"no_results": "Keine Ergebnisse für „{query}“",
|
||||
"server_hint": "Ganze Wörter durchsuchen den Server; Wortteile treffen bereits geladene Einträge",
|
||||
"searching": "Suche läuft...",
|
||||
"account_error": "{account} konnte nicht durchsucht werden",
|
||||
"recent_searches": "Letzte Suchen",
|
||||
"recurring": "Wiederkehrend",
|
||||
"no_subject": "(kein Betreff)",
|
||||
"empty_hint": "Mail, Kontakte, Termine und Dateien aller Konten durchsuchen. Zum Beispiel from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Flach nach Datum",
|
||||
"grouped": "Gruppiert",
|
||||
"load_more": "Mehr laden",
|
||||
"preview_empty": "Ergebnis auswählen, um es anzuzeigen",
|
||||
"open_full": "Öffnen",
|
||||
"view_label": "Ansicht"
|
||||
}
|
||||
}
|
||||
|
||||
+163
-116
@@ -225,6 +225,10 @@
|
||||
"and_others": "and {count} others",
|
||||
"draft": "Draft",
|
||||
"starred": "Starred",
|
||||
"pinned": "Pinned",
|
||||
"replied": "Replied",
|
||||
"forwarded": "Forwarded",
|
||||
"has_attachment": "Has attachment",
|
||||
"conversations_count": "{count} of {total} conversations",
|
||||
"conversations_count_plus": "{count}+ conversations",
|
||||
"conversations_count_simple": "{count} conversations",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Forward",
|
||||
"delete": "Delete",
|
||||
"archive": "Archive",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Star",
|
||||
"unstar": "Unstar",
|
||||
"mark_unread": "Mark as unread",
|
||||
@@ -578,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Read receipt requested (click to disable)",
|
||||
"read_receipt_off": "Request a read receipt",
|
||||
"dsn_on": "Delivery notification requested (click to disable)",
|
||||
"dsn_off": "Request a delivery notification",
|
||||
"format_plain_text": "Switch to plain text",
|
||||
"format_rich_text": "Switch to rich text (HTML)",
|
||||
"require_tls_on": "Encrypted delivery required (click to disable)",
|
||||
"require_tls_off": "Require encrypted delivery (TLS)",
|
||||
"new_message": "New Message",
|
||||
"signature_edit_hint": "Double-click to edit the signature",
|
||||
"reply": "Reply",
|
||||
@@ -632,11 +643,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Override",
|
||||
"toggle_on": "Cancel override",
|
||||
"toggle_tooltip": "Edit the From name and address freely. Mail is still sent through your identity - only the visible From header changes.",
|
||||
"toggle_tooltip": "Edit the From name and address freely. Mail is still sent through your identity.",
|
||||
"name_label": "From name",
|
||||
"name_placeholder": "Name",
|
||||
"email_label": "From email address",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "If your server doesn't accept this address as the envelope sender, {identity} is used there instead, and recipients can see it in the Return-Path header."
|
||||
},
|
||||
"use_template": "Template",
|
||||
"save_as_template": "Save as Template",
|
||||
@@ -649,6 +661,8 @@
|
||||
"upload_progress": "Uploading {uploaded} / {total}",
|
||||
"upload_cancel": "Cancel upload",
|
||||
"upload_failed": "Failed to upload {filename}",
|
||||
"attachment_too_large": "\"{name}\" is larger than the server allows ({max} per file)",
|
||||
"attachments_total_too_large": "The attachments would exceed the server's limit of {max} per message",
|
||||
"drop_files": "Drop files to attach",
|
||||
"show_less": "Show less",
|
||||
"send_failed": "Failed to send email",
|
||||
@@ -724,7 +738,11 @@
|
||||
"delete_column": "Delete column",
|
||||
"toggle_header_row": "Toggle header row",
|
||||
"delete_table": "Delete table",
|
||||
"pick_size": "Pick size"
|
||||
"pick_size": "Pick size",
|
||||
"font_size": "Font size",
|
||||
"font_size_default": "Default",
|
||||
"background_color": "Background color",
|
||||
"remove_background_color": "Remove background color"
|
||||
},
|
||||
"send_filing_warning": "Sent - but the post-send cleanup failed, a stale draft may remain."
|
||||
},
|
||||
@@ -763,6 +781,10 @@
|
||||
"email_sent": "Email sent successfully",
|
||||
"email_deleted": "Email deleted",
|
||||
"email_archived": "Email archived",
|
||||
"emails_deleted": "{count, plural, one {# email deleted} other {# emails deleted}}",
|
||||
"emails_archived": "{count, plural, one {# email archived} other {# emails archived}}",
|
||||
"error_archiving": "Failed to archive email",
|
||||
"error_updating": "Failed to update email",
|
||||
"email_starred": "Email starred",
|
||||
"email_unstarred": "Email unstarred",
|
||||
"email_marked_read": "Email marked as read",
|
||||
@@ -1027,7 +1049,7 @@
|
||||
},
|
||||
"unified_mailbox": {
|
||||
"label": "Unified Mailbox",
|
||||
"description": "Show combined folders (Inbox, Sent, etc.) for the active account and its shared folders.",
|
||||
"description": "Show combined folders (Inbox, Sent, etc.) for the active account and its shared folders. Needs more than one connected account (or a group inbox included below) before any folders appear here - with just one account, this only adds the section header. The All mail / Unread / Starred entries below are separate, admin-gated options that won't show up until your admin turns them on.",
|
||||
"cross_account": {
|
||||
"label": "Across all accounts",
|
||||
"description": "Merge the unified mailbox across every connected account instead of staying within the active account."
|
||||
@@ -1056,8 +1078,8 @@
|
||||
"description": "Show the total message count next to folders and tags, alongside the unread count. Disable to show only unread counts."
|
||||
},
|
||||
"favicon_unread_badge": {
|
||||
"label": "Unread Count on Tab Icon",
|
||||
"description": "Show the inbox unread count as a badge on the browser tab icon, so new mail is visible when the tab is not focused. Disable to keep the icon unbadged."
|
||||
"label": "Unread Count on Tab and App Icon",
|
||||
"description": "Show the inbox unread count as a badge on the browser tab icon, and on the installed app icon where supported, so new mail is visible when the tab is not focused. Disable to keep both icons unbadged."
|
||||
},
|
||||
"pro_interface": {
|
||||
"label": "Pro Interface (Experimental)",
|
||||
@@ -1142,6 +1164,7 @@
|
||||
"status_active": "Active on this device",
|
||||
"status_inactive": "Not enabled on this device",
|
||||
"status_unsupported": "This browser does not support Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"status_busy": "Working…",
|
||||
"enable": "Enable",
|
||||
"reenable": "Re-register",
|
||||
@@ -1179,7 +1202,9 @@
|
||||
"enabled": "Email notifications",
|
||||
"enabled_desc": "Show notifications when new emails arrive",
|
||||
"sound": "Notification sound",
|
||||
"sound_desc": "Play an audio alert when new emails arrive"
|
||||
"sound_desc": "Play an audio alert when new emails arrive",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Calendar Notifications",
|
||||
@@ -1239,6 +1264,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Clear search when switching folders",
|
||||
"description": "Clicking a folder (Inbox, Sent, …) clears the active search and browses that folder. When off, the search stays applied and re-runs in the folder you switch to."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Request read receipts by default",
|
||||
"description": "Pre-enable the read-receipt request when composing a new message."
|
||||
@@ -1320,7 +1349,13 @@
|
||||
},
|
||||
"auto_select_reply_identity": {
|
||||
"label": "Reply From Received Address",
|
||||
"description": "When replying, send from the address the message was originally sent to. Matches identities first; for domain catch-all deliveries, rewrites the From header to the alias while sending through your primary identity."
|
||||
"description": "When replying, send from the address the message was originally sent to. Your configured identities always match; whether other addresses on your domains count too (catch-all) is set by “Received Address Matching” below."
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Received Address Matching",
|
||||
"description": "Which received addresses count as yours. Exact address only picks one of your configured identities. Same domain also treats any other address on one of your identity domains as a catch-all alias and rewrites the From header to it. Choose exact address if those addresses are distribution lists rather than aliases.",
|
||||
"exact": "Exact address only",
|
||||
"domain": "Any address on my domains"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Signature Position",
|
||||
@@ -1751,7 +1786,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "End date must be after start date",
|
||||
"start_in_past": "Start date is in the past",
|
||||
"empty_body": "Message body is empty - recipients will receive a blank reply"
|
||||
"empty_body": "Message body is empty - recipients will receive a blank reply",
|
||||
"subject_too_long": "The subject is too long for the server (at most {max} bytes).",
|
||||
"body_too_long": "The message is too long for the server (at most {max} bytes, formatting included)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1768,6 +1805,8 @@
|
||||
"role_trash": "Trash",
|
||||
"role_junk": "Spam / Junk",
|
||||
"role_archive": "Archive",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "None",
|
||||
"create_folder": "Create Folder",
|
||||
"create_subfolder": "Create Subfolder",
|
||||
@@ -1878,6 +1917,8 @@
|
||||
"description": "Import and export your contacts",
|
||||
"group_by_letter_label": "Group by first letter",
|
||||
"group_by_letter_description": "Show alphabetical section headers in the contact list",
|
||||
"sort_by_last_name_label": "Sort by last name",
|
||||
"sort_by_last_name_description": "Order the contact list by surname so family members appear together",
|
||||
"import_label": "Import Contacts",
|
||||
"import_description": "Import contacts from a vCard (.vcf) file",
|
||||
"export_label": "Export Contacts",
|
||||
@@ -1968,6 +2009,7 @@
|
||||
"actions": "Actions",
|
||||
"add_action": "Add Action",
|
||||
"stop_processing": "Stop processing subsequent rules",
|
||||
"include_spam": "Also move messages marked as spam",
|
||||
"attachment_type_placeholder": "e.g. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Value (multiple separated by commas)",
|
||||
"condition_fields": {
|
||||
@@ -2009,6 +2051,8 @@
|
||||
"copy_to_folder": "Select folder",
|
||||
"forward_to": "Forward to email address",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "Keep a copy",
|
||||
"forward_limit": "Forward limit per message on this server: {count}. Extra forwards are skipped.",
|
||||
"reject_message": "Rejection message",
|
||||
"reject_placeholder": "Your email has been rejected",
|
||||
"label_name": "Tag name",
|
||||
@@ -2205,6 +2249,7 @@
|
||||
"new_subfolder": "New subfolder...",
|
||||
"new_folder": "New folder...",
|
||||
"rename": "Rename...",
|
||||
"share": "Share...",
|
||||
"import_email": "Import .eml or .zip...",
|
||||
"empty_folder": "Empty folder",
|
||||
"empty_folder_generic": "Empty folder",
|
||||
@@ -2234,7 +2279,10 @@
|
||||
"toast_error_rename": "Failed to rename folder",
|
||||
"toast_error_delete": "Failed to delete folder",
|
||||
"toast_error_delete_has_children": "Folder has subfolders. Remove them first.",
|
||||
"toast_error_delete_has_email": "Folder is not empty. Empty it first."
|
||||
"toast_error_delete_has_email": "Folder is not empty. Empty it first.",
|
||||
"delete_with_contents_title": "Folder is not empty",
|
||||
"delete_with_contents_message": "The folder \"{name}\" still contains {count, plural, one {1 message} other {# messages}}. Delete the folder and all of its messages? This cannot be undone.",
|
||||
"delete_with_contents_confirm": "Delete folder and messages"
|
||||
},
|
||||
"shortcuts": {
|
||||
"title": "Keyboard Shortcuts",
|
||||
@@ -2268,7 +2316,8 @@
|
||||
"search": "Focus search",
|
||||
"help": "Show shortcuts",
|
||||
"refresh": "Refresh emails",
|
||||
"select_all": "Select all"
|
||||
"select_all": "Select all",
|
||||
"global_search": "Search everything"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Expand/collapse thread"
|
||||
@@ -2720,7 +2769,10 @@
|
||||
"any_month": "Any month",
|
||||
"has_email": "Has email",
|
||||
"has_phone": "Has phone",
|
||||
"has_photo": "Has photo"
|
||||
"has_photo": "Has photo",
|
||||
"sort_by": "Sort by",
|
||||
"sort_first_name": "First name",
|
||||
"sort_last_name": "Last name"
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
@@ -2756,16 +2808,17 @@
|
||||
"duplicate": "Duplicate",
|
||||
"today_header": "Today",
|
||||
"tomorrow_header": "Tomorrow",
|
||||
"agenda_show_earlier": "Show earlier events",
|
||||
"agenda_loading": "Loading…",
|
||||
"agenda_range_start": "Showing events from {date}",
|
||||
"agenda_range_end": "Showing events until {date}",
|
||||
"export_ics": "Export as .ics",
|
||||
"copy_title": "Copy title",
|
||||
"copy_link": "Copy meeting link",
|
||||
"new_event": "New event",
|
||||
"new_all_day_event": "New all-day event",
|
||||
"new_task": "New task",
|
||||
"go_to_today": "Go to today",
|
||||
"tap_to_create": "Tap + to create one",
|
||||
"export_ics_short": ".ics",
|
||||
"copy_link_short": "Link"
|
||||
"go_to_today": "Go to today"
|
||||
},
|
||||
"detail": {
|
||||
"add_note": "Add a note...",
|
||||
@@ -2791,12 +2844,17 @@
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"delete_confirm": "Are you sure you want to delete this event?",
|
||||
"color": "Color",
|
||||
"time": "Time",
|
||||
"location_placeholder": "Place or address",
|
||||
"description_placeholder": "Notes, agenda…"
|
||||
"color": "Color"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Availability",
|
||||
"free": "Available",
|
||||
"busy": "Busy",
|
||||
"tentative": "Tentative",
|
||||
"unknown": "Not a user on this server",
|
||||
"checking": "Checking…"
|
||||
},
|
||||
"title": "Participants",
|
||||
"add": "Add participant",
|
||||
"organizer": "Organizer",
|
||||
@@ -2871,7 +2929,10 @@
|
||||
"all_events": "All events",
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"delete": "Delete"
|
||||
"delete": "Delete",
|
||||
"rsvp_title": "Respond to recurring event",
|
||||
"rsvp_description": "This is a recurring event. Which events does your response apply to?",
|
||||
"respond": "Respond"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Reminders",
|
||||
@@ -2888,8 +2949,7 @@
|
||||
"add": "Add reminder",
|
||||
"remove": "Remove reminder",
|
||||
"amount": "Reminder amount",
|
||||
"unit": "Reminder unit",
|
||||
"unit_minutes_after": "minutes after"
|
||||
"unit": "Reminder unit"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Calendar settings",
|
||||
@@ -2910,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Display event times in the month calendar view. On small screens this shows full event entries instead of dots.",
|
||||
"show_week_numbers": "Show week numbers",
|
||||
"show_week_numbers_desc": "Display week numbers in the mini-calendar",
|
||||
"calendar_free_scroll": "Free scrolling",
|
||||
"calendar_free_scroll_desc": "Scroll continuously through months, weeks and days instead of one period at a time",
|
||||
"enable_tasks": "Enable tasks",
|
||||
"enable_tasks_desc": "Show a tasks view in the calendar for managing to-dos",
|
||||
"show_tasks_on_calendar": "Show tasks on calendar",
|
||||
@@ -2921,12 +2983,11 @@
|
||||
"hover_preview_delay_1s": "1-second delay",
|
||||
"hover_preview_delay_2s": "2-second delay",
|
||||
"hover_preview_off": "Disabled",
|
||||
"organizer_identity": "Organize invitations as",
|
||||
"organizer_identity_desc": "The calendar address used as organizer when you invite people to events.",
|
||||
"organizer_identity_failed": "Failed to change the organizer address",
|
||||
"show_birthday_calendar": "Contact birthday calendar",
|
||||
"show_birthday_calendar_desc": "Show a virtual calendar with birthdays from your contacts",
|
||||
"time_zone": "Time zone",
|
||||
"time_zone_desc": "Zone used for new events and for interpreting the calendar. \"Device\" follows the phone.",
|
||||
"time_zone_auto": "Device time zone",
|
||||
"show_week_numbers_mobile_desc": "Display week numbers in the month view."
|
||||
"show_birthday_calendar_desc": "Show a virtual calendar with birthdays from your contacts"
|
||||
},
|
||||
"days": {
|
||||
"monday": "Monday",
|
||||
@@ -2982,6 +3043,9 @@
|
||||
"alert_now": "Starting now",
|
||||
"alert_in_minutes": "In {count} min",
|
||||
"invitation_sent": "Invitations sent",
|
||||
"invitations_denied_title": "Invitations can't be sent",
|
||||
"invitations_denied": "The server refused to send the invitations: {reason}. Save the event without sending them?",
|
||||
"save_without_invitations": "Save without invitations",
|
||||
"rsvp_updated": "Response updated",
|
||||
"rsvp_error": "Failed to update response",
|
||||
"event_duplicated": "Event duplicated",
|
||||
@@ -3011,7 +3075,7 @@
|
||||
"supported_formats": "Supports iCalendar (.ics) files",
|
||||
"url_description": "Enter the URL of an external iCalendar (.ics) feed to import events.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "Supports CalDAV and iCalendar (.ics) URLs",
|
||||
"url_hint": "Supports iCalendar (.ics) URLs",
|
||||
"fetch": "Fetch",
|
||||
"invalid_url": "Please enter a valid URL",
|
||||
"url_fetch_failed": "Failed to fetch calendar from URL",
|
||||
@@ -3076,15 +3140,7 @@
|
||||
"kind_events": "Events",
|
||||
"kind_tasks": "Tasks",
|
||||
"kind_both": "Events and tasks",
|
||||
"kind_hint": "Decides which apps see this calendar when it is synced over CalDAV. It cannot be changed later.",
|
||||
"new_calendar": "New calendar",
|
||||
"edit_calendar": "Edit calendar",
|
||||
"description_placeholder": "Optional",
|
||||
"clear_title": "Remove all events?",
|
||||
"clear_description": "Every event in this calendar will be deleted. Events that also belong to another calendar are only unlinked.",
|
||||
"clear_confirm": "Remove all",
|
||||
"delete_title": "Delete calendar?",
|
||||
"delete_description": "The calendar and all of its events will be deleted. This cannot be undone."
|
||||
"kind_hint": "Decides which apps see this calendar when it is synced over CalDAV. It cannot be changed later."
|
||||
},
|
||||
"subscription": {
|
||||
"title": "iCal Subscription",
|
||||
@@ -3163,59 +3219,26 @@
|
||||
"quick_add_placeholder": "Add a task...",
|
||||
"due_today": "Today",
|
||||
"due_tomorrow": "Tomorrow",
|
||||
"overdue": "Overdue",
|
||||
"add_placeholder": "Add a task…",
|
||||
"add_time": "Add time"
|
||||
},
|
||||
"agenda": {
|
||||
"no_upcoming": "No upcoming events",
|
||||
"days_clear": "The next {count} days are clear."
|
||||
},
|
||||
"drawer": {
|
||||
"no_calendars": "No calendars yet.",
|
||||
"my_calendars": "My calendars",
|
||||
"shared_with_me": "Shared with me",
|
||||
"subscribed": "Subscribed"
|
||||
},
|
||||
"share": {
|
||||
"title": "Share",
|
||||
"shared_with": "Shared with",
|
||||
"add_people": "Add people",
|
||||
"search_placeholder": "Search by name or email",
|
||||
"no_principals": "Sharing is not available on this server.",
|
||||
"no_matches": "No matches",
|
||||
"role_free_busy": "Free/busy",
|
||||
"role_viewer": "Viewer",
|
||||
"role_editor": "Editor",
|
||||
"role_manager": "Manager",
|
||||
"role_custom": "Custom",
|
||||
"error": "Failed to update sharing"
|
||||
},
|
||||
"invitation": {
|
||||
"reading": "Reading invitation…",
|
||||
"title": "Calendar invitation",
|
||||
"cancelled": "This event was cancelled",
|
||||
"reply": "A participant replied to this invitation",
|
||||
"organizer": "Organizer",
|
||||
"join_video": "Join video call",
|
||||
"already_in_calendar": "Already in your calendar",
|
||||
"added": "Added to calendar",
|
||||
"add_error": "Could not add the event",
|
||||
"response_sent": "Response sent",
|
||||
"response_error": "Could not send your response",
|
||||
"add_to_calendar": "Add to calendar",
|
||||
"no_writable_calendar": "No writable calendar available",
|
||||
"accept": "Yes",
|
||||
"tentative": "Maybe",
|
||||
"decline": "No",
|
||||
"trust_verified": "Sender verified",
|
||||
"trust_sender_mismatch_unverified": "The sender does not match the organizer and the message is not authenticated.",
|
||||
"trust_authentication_failed": "This message failed sender authentication (SPF/DKIM/DMARC).",
|
||||
"trust_sender_mismatch": "The sender differs from the event organizer.",
|
||||
"trust_authentication_missing": "The sender could not be verified."
|
||||
},
|
||||
"event_modal": {
|
||||
"participant_placeholder": "Add participant by email"
|
||||
"overdue": "Overdue"
|
||||
}
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Someone",
|
||||
"invited": "{name} invited you to \"{title}\"",
|
||||
"updated": "{name} updated \"{title}\"",
|
||||
"cancelled": "{name} cancelled \"{title}\"",
|
||||
"untitled": "Untitled event"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Someone",
|
||||
"shared": "{name} shared the {kind} \"{object}\" with you",
|
||||
"changed": "{name} changed your access to the {kind} \"{object}\"",
|
||||
"revoked": "{name} removed your access to the {kind} \"{object}\"",
|
||||
"object": {
|
||||
"folder": "folder",
|
||||
"calendar": "calendar",
|
||||
"address_book": "address book",
|
||||
"files": "file folder"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
@@ -3256,6 +3279,8 @@
|
||||
"has_attachment": "Attachments",
|
||||
"date_after": "After",
|
||||
"date_before": "Before",
|
||||
"size_min": "Larger than (KB)",
|
||||
"size_max": "Smaller than (KB)",
|
||||
"starred": "Starred",
|
||||
"unread": "Unread",
|
||||
"read": "Read",
|
||||
@@ -3297,6 +3322,10 @@
|
||||
"hint": "Click any email on the left to get started, or take the tour below."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Edit document",
|
||||
"office_loading": "Opening document editor…",
|
||||
"office_error": "The document editor could not be opened.",
|
||||
"office_close": "Close editor",
|
||||
"open_in_new_tab": "Open in new tab",
|
||||
"title": "Files",
|
||||
"search_placeholder": "Search files...",
|
||||
@@ -3309,6 +3338,8 @@
|
||||
"rename": "Rename",
|
||||
"rename_title": "Rename",
|
||||
"new_name": "New name",
|
||||
"name_forbidden_chars": "The server does not allow these characters in names: {chars}",
|
||||
"name_reserved": "This name is reserved on the server.",
|
||||
"delete": "Delete",
|
||||
"delete_confirm_title": "Delete resource",
|
||||
"delete_confirm_message": "Are you sure you want to delete \"{name}\"? This cannot be undone.",
|
||||
@@ -3409,31 +3440,7 @@
|
||||
"disabled_description": "Large file uploads via WebDAV can cause Stalwart/RocksDB instability, including out-of-memory crashes and unrecoverable disk usage. Deleted files may not be immediately purged from blob storage. This feature is not recommended for production environments.",
|
||||
"stability_warning": "Large file uploads can cause server instability. Deleted files may not be immediately purged from storage. Use with caution.",
|
||||
"migration_title": "Updating your files…",
|
||||
"migration_description": "Organising folders and files into their proper structure. This happens only once.",
|
||||
"move_to": "Move to…",
|
||||
"sharing_access": "Sharing & access",
|
||||
"shared_with": "Shared with",
|
||||
"share_add_people": "Add people",
|
||||
"share_search_placeholder": "Search by name or email",
|
||||
"share_no_users": "No other users found",
|
||||
"share_no_matches": "No matches",
|
||||
"share_folder_hint": "Sharing a folder shares everything inside it.",
|
||||
"share_error": "Failed to update sharing",
|
||||
"share_remove": "Remove access",
|
||||
"share_empty": "Not shared with anyone yet",
|
||||
"share_role_viewer": "Viewer",
|
||||
"share_role_editor": "Editor",
|
||||
"share_role_manager": "Manager",
|
||||
"share_role_custom": "Custom",
|
||||
"done": "Done",
|
||||
"invalid_name": "Invalid name",
|
||||
"invalid_name_slash": "Names cannot contain \"/\".",
|
||||
"upload_unavailable": "The file picker is not installed in this build. Rebuild the app to enable uploads.",
|
||||
"selected_count": "{count} selected",
|
||||
"folder_empty": "This folder is empty",
|
||||
"delete_item_title": "Delete this item?",
|
||||
"delete_items_title": "Delete {count} items?",
|
||||
"delete_cascade_hint": "Folders are deleted along with everything inside them. This can't be undone."
|
||||
"migration_description": "Organising folders and files into their proper structure. This happens only once."
|
||||
},
|
||||
"smime": {
|
||||
"your_certificates": "Your Certificates",
|
||||
@@ -3594,5 +3601,45 @@
|
||||
"copy_event": "Copy link to event",
|
||||
"copied": "Link copied",
|
||||
"copy_failed": "Couldn't copy the link"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Completing authorization…",
|
||||
"completing_message": "Please wait while the connection is established.",
|
||||
"received_title": "Authorization received",
|
||||
"received_message": "You can return to the tab where you started the connection.",
|
||||
"failed_title": "Authorization failed",
|
||||
"denied": "Authorization was denied.",
|
||||
"failed_detail": "Authorization failed: {error}",
|
||||
"missing_params": "Missing authorization code or state.",
|
||||
"delivery_failed": "The authorization result could not be delivered to the app tab. Please close this window and try again."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Search everything",
|
||||
"placeholder": "Search mail, contacts, calendar, files...",
|
||||
"scope_label": "Search in",
|
||||
"account_label": "Account",
|
||||
"scope_all": "All",
|
||||
"scope_mail": "Mail",
|
||||
"scope_contacts": "Contacts",
|
||||
"scope_calendar": "Calendar",
|
||||
"scope_files": "Files",
|
||||
"all_accounts": "All accounts",
|
||||
"show_all": "Show all results",
|
||||
"no_results": "No results for “{query}”",
|
||||
"server_hint": "Complete words search the server; partial words match already-loaded items",
|
||||
"searching": "Searching...",
|
||||
"account_error": "Couldn't search {account}",
|
||||
"recent_searches": "Recent searches",
|
||||
"recurring": "Recurring",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Search mail, contacts, events and files across all accounts. Try from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Flat by date",
|
||||
"grouped": "Grouped",
|
||||
"load_more": "Load more",
|
||||
"preview_empty": "Select a result to preview it",
|
||||
"open_full": "Open",
|
||||
"view_label": "View"
|
||||
}
|
||||
}
|
||||
|
||||
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "y {count} más",
|
||||
"draft": "Borrador",
|
||||
"starred": "Destacado",
|
||||
"pinned": "Fijado",
|
||||
"replied": "Respondido",
|
||||
"forwarded": "Reenviado",
|
||||
"has_attachment": "Tiene adjunto",
|
||||
"conversations_count": "{count} de {total} conversaciones",
|
||||
"conversations_count_plus": "{count}+ conversaciones",
|
||||
"conversations_count_simple": "{count} conversaciones",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Reenviar",
|
||||
"delete": "Eliminar",
|
||||
"archive": "Archivar",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Destacar",
|
||||
"unstar": "Quitar destacado",
|
||||
"mark_unread": "Marcar como no leído",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Mover",
|
||||
"print": "Imprimir",
|
||||
"view_source": "Ver código fuente",
|
||||
|
||||
"fullscreen": "Pantalla completa",
|
||||
|
||||
"exit_fullscreen": "Salir de pantalla completa",
|
||||
"export_email": "Exportar como .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Reenviar como archivo adjunto",
|
||||
"import_email": "Importar .eml o .zip",
|
||||
"keyboard_shortcuts": "Atajos de teclado (?)",
|
||||
"email_source": "Código Fuente del Correo",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Confirmación de lectura solicitada (haz clic para desactivar)",
|
||||
"read_receipt_off": "Solicitar confirmación de lectura",
|
||||
"dsn_on": "Notificación de entrega solicitada (clic para desactivar)",
|
||||
"dsn_off": "Solicitar notificación de entrega",
|
||||
"format_plain_text": "Cambiar a texto sin formato",
|
||||
"format_rich_text": "Cambiar a texto enriquecido (HTML)",
|
||||
"require_tls_on": "Entrega cifrada obligatoria (clic para desactivar)",
|
||||
"require_tls_off": "Exigir entrega cifrada (TLS)",
|
||||
"new_message": "Nuevo Mensaje",
|
||||
"signature_edit_hint": "Haz doble clic para editar la firma",
|
||||
"reply": "Responder",
|
||||
@@ -631,11 +640,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Anular",
|
||||
"toggle_on": "Cancelar anulación",
|
||||
"toggle_tooltip": "Edita libremente el nombre y la dirección del remitente. El correo aún se envía a través de tu identidad - solo cambia el encabezado De visible.",
|
||||
"toggle_tooltip": "Edita libremente el nombre y la dirección del remitente. El correo aún se envía a través de tu identidad.",
|
||||
"name_label": "Nombre del remitente",
|
||||
"name_placeholder": "Nombre",
|
||||
"email_label": "Dirección de correo del remitente",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "Si tu servidor no acepta esta dirección como remitente del sobre, se usa {identity} en su lugar, y los destinatarios pueden verla en el encabezado Return-Path."
|
||||
},
|
||||
"use_template": "Plantilla",
|
||||
"save_as_template": "Guardar como plantilla",
|
||||
@@ -648,6 +658,8 @@
|
||||
"upload_progress": "Subiendo {uploaded} / {total}",
|
||||
"upload_cancel": "Cancelar subida",
|
||||
"upload_failed": "Error al subir {filename}",
|
||||
"attachment_too_large": "\"{name}\" supera el tamaño que permite el servidor ({max} por archivo)",
|
||||
"attachments_total_too_large": "Los adjuntos superarían el límite del servidor de {max} por mensaje",
|
||||
"send_failed": "Error al enviar el correo",
|
||||
"smime_sign_on": "Firma S/MIME activada",
|
||||
"smime_sign_off": "Firma S/MIME desactivada",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Eliminar columna",
|
||||
"toggle_header_row": "Alternar fila de encabezado",
|
||||
"delete_table": "Eliminar tabla",
|
||||
"pick_size": "Elegir tamaño"
|
||||
"pick_size": "Elegir tamaño",
|
||||
"font_size": "Tamaño de fuente",
|
||||
"font_size_default": "Predeterminado",
|
||||
"background_color": "Color de fondo",
|
||||
"remove_background_color": "Quitar color de fondo"
|
||||
},
|
||||
"send_filing_warning": "Enviado - pero la limpieza posterior falló, puede quedar un borrador obsoleto."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "Correo enviado exitosamente",
|
||||
"email_deleted": "Correo eliminado",
|
||||
"email_archived": "Correo archivado",
|
||||
"emails_deleted": "{count, plural, one {# correo eliminado} other {# correos eliminados}}",
|
||||
"emails_archived": "{count, plural, one {# correo archivado} other {# correos archivados}}",
|
||||
"error_archiving": "No se pudo archivar el correo",
|
||||
"error_updating": "No se pudo actualizar el correo",
|
||||
"email_starred": "Correo destacado",
|
||||
"email_unstarred": "Correo sin destacar",
|
||||
"email_marked_read": "Correo marcado como leído",
|
||||
@@ -1143,7 +1163,9 @@
|
||||
"enabled": "Notificaciones de correo",
|
||||
"enabled_desc": "Mostrar notificaciones cuando lleguen nuevos correos",
|
||||
"sound": "Sonido de notificación",
|
||||
"sound_desc": "Reproducir un sonido cuando lleguen nuevos correos"
|
||||
"sound_desc": "Reproducir un sonido cuando lleguen nuevos correos",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Notificaciones de calendario",
|
||||
@@ -1172,6 +1194,7 @@
|
||||
"status_busy": "Trabajando…",
|
||||
"status_inactive": "No habilitado en este dispositivo",
|
||||
"status_unsupported": "Este navegador no admite Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"title": "Notificaciones en segundo plano",
|
||||
"devices_title": "Dispositivos registrados",
|
||||
"devices_desc": "Todos los dispositivos registrados para recibir notificaciones en segundo plano de esta cuenta. Al revocar uno, deja de recibirlas de inmediato.",
|
||||
@@ -1238,6 +1261,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Borrar la búsqueda al cambiar de carpeta",
|
||||
"description": "Al hacer clic en una carpeta (Bandeja de entrada, Enviados…) se borra la búsqueda activa y se muestra esa carpeta. Si está desactivado, la búsqueda se mantiene y se vuelve a ejecutar en la carpeta a la que cambies."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Solicitar confirmaciones de lectura de forma predeterminada",
|
||||
"description": "Activar previamente la solicitud de confirmación de lectura al redactar un mensaje nuevo."
|
||||
@@ -1316,6 +1343,12 @@
|
||||
"label": "Seleccionar dirección de respuesta automáticamente",
|
||||
"description": "Al responder, cambia automáticamente la dirección del remitente a la identidad que recibió el mensaje original"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Coincidencia de la dirección de recepción",
|
||||
"description": "Qué direcciones de recepción cuentan como tuyas. «Solo la dirección exacta» elige únicamente una de tus identidades configuradas; «mismo dominio» también trata cualquier otra dirección de tus dominios de identidad como un alias catch-all y reescribe la cabecera De con ella. Elige la dirección exacta si esas direcciones son listas de distribución.",
|
||||
"exact": "Solo la dirección exacta",
|
||||
"domain": "Cualquier dirección de mis dominios"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Posición de la firma",
|
||||
"description": "Dónde insertar tu firma en respuestas y reenvíos. Encima del texto citado se lee de forma natural como cierre de la respuesta; debajo mantiene el mensaje original contiguo.",
|
||||
@@ -1746,7 +1779,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "La fecha de fin debe ser posterior a la fecha de inicio",
|
||||
"start_in_past": "La fecha de inicio está en el pasado",
|
||||
"empty_body": "El cuerpo del mensaje está vacío - los destinatarios recibirán una respuesta en blanco"
|
||||
"empty_body": "El cuerpo del mensaje está vacío - los destinatarios recibirán una respuesta en blanco",
|
||||
"subject_too_long": "El asunto es demasiado largo para el servidor (máximo {max} bytes).",
|
||||
"body_too_long": "El mensaje es demasiado largo para el servidor (máximo {max} bytes, formato incluido)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1763,6 +1798,8 @@
|
||||
"role_trash": "Papelera",
|
||||
"role_junk": "Spam / No deseado",
|
||||
"role_archive": "Archivo",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Ninguno",
|
||||
"create_folder": "Crear carpeta",
|
||||
"create_subfolder": "Crear subcarpeta",
|
||||
@@ -1882,7 +1919,9 @@
|
||||
"categories_description": "Renombrar categorías de contactos",
|
||||
"no_categories": "No se encontraron categorías",
|
||||
"group_by_letter_description": "Mostrar encabezados alfabéticos en la lista de contactos",
|
||||
"group_by_letter_label": "Agrupar por primera letra"
|
||||
"group_by_letter_label": "Agrupar por primera letra",
|
||||
"sort_by_last_name_label": "Ordenar por apellido",
|
||||
"sort_by_last_name_description": "Ordenar la lista de contactos por apellido para que los miembros de una familia aparezcan juntos"
|
||||
},
|
||||
"downloads": {
|
||||
"title": "Descargas",
|
||||
@@ -1963,6 +2002,7 @@
|
||||
"actions": "Acciones",
|
||||
"add_action": "Agregar acción",
|
||||
"stop_processing": "Detener el procesamiento de reglas posteriores",
|
||||
"include_spam": "Mover también los mensajes marcados como spam",
|
||||
"attachment_type_placeholder": "p. ej. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Valor (varios separados por comas)",
|
||||
"condition_fields": {
|
||||
@@ -2004,6 +2044,8 @@
|
||||
"copy_to_folder": "Seleccionar carpeta",
|
||||
"forward_to": "Reenviar a dirección de correo",
|
||||
"forward_placeholder": "correo@ejemplo.com",
|
||||
"forward_keep_copy": "Conservar una copia",
|
||||
"forward_limit": "Límite de reenvíos por mensaje en este servidor: {count}. Los reenvíos adicionales se omiten.",
|
||||
"reject_message": "Mensaje de rechazo",
|
||||
"reject_placeholder": "Su correo ha sido rechazado",
|
||||
"label_name": "Nombre de la etiqueta",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Nueva subcarpeta...",
|
||||
"new_folder": "Nueva carpeta...",
|
||||
"rename": "Renombrar...",
|
||||
"share": "Compartir...",
|
||||
"import_email": "Importar .eml o .zip...",
|
||||
"empty_folder": "Vaciar carpeta",
|
||||
"empty_folder_generic": "Vaciar carpeta",
|
||||
@@ -2234,6 +2277,9 @@
|
||||
"toast_error_delete": "Error al eliminar la carpeta",
|
||||
"toast_error_delete_has_children": "La carpeta tiene subcarpetas. Elimínelas primero.",
|
||||
"toast_error_delete_has_email": "La carpeta no está vacía. Vacíela primero.",
|
||||
"delete_with_contents_title": "La carpeta no está vacía",
|
||||
"delete_with_contents_message": "La carpeta \"{name}\" aún contiene {count, plural, one {1 mensaje} other {# mensajes}}. ¿Eliminar la carpeta y todos sus mensajes? Esta acción no se puede deshacer.",
|
||||
"delete_with_contents_confirm": "Eliminar carpeta y mensajes",
|
||||
"placeholder_folder_name": "Nombre de carpeta",
|
||||
"create": "Crear",
|
||||
"rename_confirm": "Renombrar"
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Enfocar búsqueda",
|
||||
"help": "Mostrar atajos",
|
||||
"refresh": "Actualizar correos",
|
||||
"select_all": "Seleccionar todo"
|
||||
"select_all": "Seleccionar todo",
|
||||
"global_search": "Buscar en todo"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Expandir/contraer conversación"
|
||||
@@ -2721,7 +2768,10 @@
|
||||
"any_month": "Cualquier mes",
|
||||
"has_email": "Con correo",
|
||||
"has_phone": "Con teléfono",
|
||||
"has_photo": "Con foto"
|
||||
"has_photo": "Con foto",
|
||||
"sort_by": "Ordenar por",
|
||||
"sort_first_name": "Nombre",
|
||||
"sort_last_name": "Apellido"
|
||||
},
|
||||
"open_categories": "Abrir categorías"
|
||||
},
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "Duplicar",
|
||||
"today_header": "Hoy",
|
||||
"tomorrow_header": "Mañana",
|
||||
"agenda_show_earlier": "Mostrar eventos anteriores",
|
||||
"agenda_loading": "Cargando…",
|
||||
"agenda_range_start": "Eventos desde el {date}",
|
||||
"agenda_range_end": "Eventos hasta el {date}",
|
||||
"export_ics": "Exportar como .ics",
|
||||
"copy_title": "Copiar título",
|
||||
"copy_link": "Copiar enlace de reunión",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "Color"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Disponibilidad",
|
||||
"free": "Disponible",
|
||||
"busy": "Ocupado",
|
||||
"tentative": "Provisional",
|
||||
"unknown": "No es un usuario de este servidor",
|
||||
"checking": "Comprobando…"
|
||||
},
|
||||
"title": "Participantes",
|
||||
"add": "Agregar participante",
|
||||
"organizer": "Organizador",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Todos los eventos",
|
||||
"cancel": "Cancelar",
|
||||
"save": "Guardar",
|
||||
"delete": "Eliminar"
|
||||
"delete": "Eliminar",
|
||||
"rsvp_title": "Responder a evento recurrente",
|
||||
"rsvp_description": "Este es un evento recurrente. ¿A qué eventos se aplica su respuesta?",
|
||||
"respond": "Responder"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Recordatorio",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Mostrar las horas de los eventos en la vista mensual del calendario",
|
||||
"show_week_numbers": "Mostrar números de semana",
|
||||
"show_week_numbers_desc": "Mostrar los números de semana en el minicalendario",
|
||||
"calendar_free_scroll": "Desplazamiento libre",
|
||||
"calendar_free_scroll_desc": "Desplazarse de forma continua por meses, semanas y días en lugar de un periodo cada vez",
|
||||
"enable_tasks": "Activar tareas",
|
||||
"enable_tasks_desc": "Mostrar una vista de tareas en el calendario para gestionar tareas pendientes",
|
||||
"show_tasks_on_calendar": "Mostrar tareas en el calendario",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "Retraso de 1 segundo",
|
||||
"hover_preview_delay_2s": "Retraso de 2 segundos",
|
||||
"hover_preview_off": "Desactivado",
|
||||
"organizer_identity": "Organizar invitaciones como",
|
||||
"organizer_identity_desc": "La dirección de calendario usada como organizador al invitar a personas a eventos.",
|
||||
"organizer_identity_failed": "No se pudo cambiar la dirección del organizador",
|
||||
"show_birthday_calendar": "Calendario de cumpleaños",
|
||||
"show_birthday_calendar_desc": "Mostrar un calendario virtual con los cumpleaños de tus contactos"
|
||||
},
|
||||
@@ -2947,6 +3017,9 @@
|
||||
"alert_now": "Comienza ahora",
|
||||
"alert_in_minutes": "En {count} min",
|
||||
"invitation_sent": "Invitaciones enviadas",
|
||||
"invitations_denied_title": "No se pueden enviar las invitaciones",
|
||||
"invitations_denied": "El servidor rechazó enviar las invitaciones: {reason}. ¿Guardar el evento sin enviarlas?",
|
||||
"save_without_invitations": "Guardar sin invitaciones",
|
||||
"rsvp_updated": "Respuesta actualizada",
|
||||
"rsvp_error": "Error al actualizar la respuesta",
|
||||
"event_duplicated": "Evento duplicado",
|
||||
@@ -2975,7 +3048,7 @@
|
||||
"supported_formats": "Archivos iCalendar (.ics) compatibles",
|
||||
"url_description": "Introduce la URL de un feed iCalendar (.ics) externo para importar eventos.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "Compatible con URLs CalDAV e iCalendar (.ics)",
|
||||
"url_hint": "Compatible con URLs iCalendar (.ics)",
|
||||
"fetch": "Obtener",
|
||||
"invalid_url": "Introduce una URL válida",
|
||||
"url_fetch_failed": "No se pudo obtener el calendario desde la URL",
|
||||
@@ -3164,6 +3237,8 @@
|
||||
"has_attachment": "Adjuntos",
|
||||
"date_after": "Después de",
|
||||
"date_before": "Antes de",
|
||||
"size_min": "Mayor que (KB)",
|
||||
"size_max": "Menor que (KB)",
|
||||
"starred": "Destacados",
|
||||
"unread": "No leídos",
|
||||
"read": "Leídos",
|
||||
@@ -3205,6 +3280,10 @@
|
||||
"hint": "Haz clic en un correo a la izquierda para empezar, o inicia el tour."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Editar documento",
|
||||
"office_loading": "Abriendo el editor de documentos…",
|
||||
"office_error": "No se pudo abrir el editor de documentos.",
|
||||
"office_close": "Cerrar editor",
|
||||
"open_in_new_tab": "Abrir en una pestaña nueva",
|
||||
"title": "Archivos",
|
||||
"search_placeholder": "Buscar archivos...",
|
||||
@@ -3217,6 +3296,8 @@
|
||||
"rename": "Renombrar",
|
||||
"rename_title": "Renombrar",
|
||||
"new_name": "Nuevo nombre",
|
||||
"name_forbidden_chars": "El servidor no permite estos caracteres en los nombres: {chars}",
|
||||
"name_reserved": "Este nombre está reservado en el servidor.",
|
||||
"delete": "Eliminar",
|
||||
"delete_confirm_title": "Eliminar recurso",
|
||||
"delete_confirm_message": "¿Está seguro de que desea eliminar \"{name}\"? Esta acción no se puede deshacer.",
|
||||
@@ -3451,6 +3532,25 @@
|
||||
"unified_mailbox": {
|
||||
"search_unavailable": "La búsqueda no está disponible en la vista unificada"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Alguien",
|
||||
"invited": "{name} te ha invitado a \"{title}\"",
|
||||
"updated": "{name} ha actualizado \"{title}\"",
|
||||
"cancelled": "{name} ha cancelado \"{title}\"",
|
||||
"untitled": "Evento sin título"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Alguien",
|
||||
"shared": "{name} ha compartido contigo {kind} \"{object}\"",
|
||||
"changed": "{name} ha cambiado tu acceso a {kind} \"{object}\"",
|
||||
"revoked": "{name} ha retirado tu acceso a {kind} \"{object}\"",
|
||||
"object": {
|
||||
"folder": "la carpeta",
|
||||
"calendar": "el calendario",
|
||||
"address_book": "la libreta de direcciones",
|
||||
"files": "la carpeta de archivos"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Compartir «{name}»",
|
||||
"description": "Concede acceso a otros usuarios o grupos de este servidor. Los cambios surten efecto inmediatamente.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Copiar enlace del evento",
|
||||
"copied": "Enlace copiado",
|
||||
"copy_failed": "No se pudo copiar el enlace"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Completando la autorización…",
|
||||
"completing_message": "Por favor, espera mientras se establece la conexión.",
|
||||
"received_title": "Autorización recibida",
|
||||
"received_message": "Puedes volver a la pestaña donde iniciaste la conexión.",
|
||||
"failed_title": "Error de autorización",
|
||||
"denied": "Se denegó la autorización.",
|
||||
"failed_detail": "Error de autorización: {error}",
|
||||
"missing_params": "Falta el código de autorización o el estado.",
|
||||
"delivery_failed": "No se pudo entregar el resultado de la autorización a la pestaña de la aplicación. Cierre esta ventana e inténtelo de nuevo."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Buscar en todo",
|
||||
"placeholder": "Buscar correo, contactos, calendario, archivos...",
|
||||
"scope_label": "Buscar en",
|
||||
"account_label": "Cuenta",
|
||||
"scope_all": "Todo",
|
||||
"scope_mail": "Correo",
|
||||
"scope_contacts": "Contactos",
|
||||
"scope_calendar": "Calendario",
|
||||
"scope_files": "Archivos",
|
||||
"all_accounts": "Todas las cuentas",
|
||||
"show_all": "Mostrar todos los resultados",
|
||||
"no_results": "Sin resultados para “{query}”",
|
||||
"server_hint": "Las palabras completas buscan en el servidor; las parciales coinciden con lo ya cargado",
|
||||
"searching": "Buscando...",
|
||||
"account_error": "No se pudo buscar en {account}",
|
||||
"recent_searches": "Búsquedas recientes",
|
||||
"recurring": "Periódico",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Busca correo, contactos, eventos y archivos en todas las cuentas. Prueba from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Plano por fecha",
|
||||
"grouped": "Agrupado",
|
||||
"load_more": "Cargar más",
|
||||
"preview_empty": "Selecciona un resultado para previsualizarlo",
|
||||
"open_full": "Abrir",
|
||||
"view_label": "Vista"
|
||||
}
|
||||
}
|
||||
|
||||
+152
-12
@@ -225,6 +225,10 @@
|
||||
"and_others": "و {count} نفر دیگر",
|
||||
"draft": "پیشنویس",
|
||||
"starred": "ستارهدار",
|
||||
"pinned": "سنجاقشده",
|
||||
"replied": "پاسخ داده شده",
|
||||
"forwarded": "بازارسال شده",
|
||||
"has_attachment": "دارای پیوست",
|
||||
"conversations_count": "{count} از {total} مکالمه",
|
||||
"conversations_count_plus": "+{count} مکالمه",
|
||||
"conversations_count_simple": "{count} مکالمه",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "ارسال مجدد",
|
||||
"delete": "حذف",
|
||||
"archive": "بایگانی",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "ستارهدار کردن",
|
||||
"unstar": "حذف ستاره",
|
||||
"mark_unread": "علامتگذاری خوانده نشده",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "انتقال",
|
||||
"print": "چاپ",
|
||||
"view_source": "مشاهده منبع",
|
||||
|
||||
"fullscreen": "تمامصفحه",
|
||||
|
||||
"exit_fullscreen": "خروج از تمامصفحه",
|
||||
"export_email": "خروجی .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "ارسال بهعنوان پیوست",
|
||||
"import_email": "وارد کردن .eml یا .zip",
|
||||
"keyboard_shortcuts": "میانبرهای صفحه کلید (?)",
|
||||
"email_source": "منبع ایمیل",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "درخواست تأیید خواندن فعال (کلیک برای غیرفعال کردن)",
|
||||
"read_receipt_off": "درخواست تأیید خواندن",
|
||||
"dsn_on": "اعلان تحویل درخواست شد (برای غیرفعال کردن کلیک کنید)",
|
||||
"dsn_off": "درخواست اعلان تحویل",
|
||||
"format_plain_text": "تغییر به متن ساده",
|
||||
"format_rich_text": "تغییر به متن غنی (HTML)",
|
||||
"require_tls_on": "تحویل رمزگذاریشده الزامی است (برای غیرفعال کردن کلیک کنید)",
|
||||
"require_tls_off": "الزام تحویل رمزگذاریشده (TLS)",
|
||||
"new_message": "پیام جدید",
|
||||
"signature_edit_hint": "برای ویرایش امضا دوبار کلیک کنید",
|
||||
"reply": "پاسخ",
|
||||
@@ -638,7 +647,8 @@
|
||||
"name_label": "نام فرستنده",
|
||||
"name_placeholder": "نام",
|
||||
"email_label": "آدرس ایمیل فرستنده",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "اگر سرور شما این آدرس را بهعنوان فرستندهٔ پاکت نپذیرد، بهجای آن {identity} استفاده میشود و گیرندگان میتوانند آن را در سرآیند Return-Path ببینند."
|
||||
},
|
||||
"use_template": "قالب",
|
||||
"save_as_template": "ذخیره به عنوان قالب",
|
||||
@@ -651,6 +661,8 @@
|
||||
"upload_progress": "در حال بارگذاری {uploaded} / {total}",
|
||||
"upload_cancel": "لغو بارگذاری",
|
||||
"upload_failed": "بارگذاری {filename} ناموفق بود",
|
||||
"attachment_too_large": "«{name}» بزرگتر از حدی است که سرور اجازه میدهد ({max} برای هر فایل)",
|
||||
"attachments_total_too_large": "پیوستها از سقف {max} سرور برای هر پیام بیشتر میشوند",
|
||||
"drop_files": "فایلها را اینجا رها کنید",
|
||||
"show_less": "نمایش کمتر",
|
||||
"send_failed": "ارسال ایمیل ناموفق بود",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "حذف ستون",
|
||||
"toggle_header_row": "تغییر وضعیت ردیف سرصفحه",
|
||||
"delete_table": "حذف جدول",
|
||||
"pick_size": "انتخاب اندازه"
|
||||
"pick_size": "انتخاب اندازه",
|
||||
"font_size": "اندازه فونت",
|
||||
"font_size_default": "پیشفرض",
|
||||
"background_color": "رنگ پسزمینه",
|
||||
"remove_background_color": "حذف رنگ پسزمینه"
|
||||
},
|
||||
"send_filing_warning": "ارسال شد - اما پاکسازی پس از ارسال ناموفق بود، ممکن است پیشنویس قدیمی باقی بماند."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "ایمیل با موفقیت ارسال شد",
|
||||
"email_deleted": "ایمیل حذف شد",
|
||||
"email_archived": "ایمیل بایگانی شد",
|
||||
"emails_deleted": "{count, plural, other {# ایمیل حذف شد}}",
|
||||
"emails_archived": "{count, plural, other {# ایمیل بایگانی شد}}",
|
||||
"error_archiving": "بایگانی ایمیل ناموفق بود",
|
||||
"error_updating": "بهروزرسانی ایمیل ناموفق بود",
|
||||
"email_starred": "ایمیل ستارهدار شد",
|
||||
"email_unstarred": "ستاره ایمیل برداشته شد",
|
||||
"email_marked_read": "ایمیل خوانده شده علامتگذاری شد",
|
||||
@@ -1144,6 +1164,7 @@
|
||||
"status_active": "فعال روی این دستگاه",
|
||||
"status_inactive": "فعال نشده",
|
||||
"status_unsupported": "مرورگر پشتیبانی نمیکند",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"status_busy": "در حال کار…",
|
||||
"enable": "فعال کردن",
|
||||
"reenable": "ثبت مجدد",
|
||||
@@ -1181,7 +1202,9 @@
|
||||
"enabled": "اعلانهای ایمیل",
|
||||
"enabled_desc": "نمایش اعلان هنگام ایمیل جدید",
|
||||
"sound": "صدای اعلان",
|
||||
"sound_desc": "پخش هشدار صوتی"
|
||||
"sound_desc": "پخش هشدار صوتی",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "اعلانهای تقویم",
|
||||
@@ -1241,6 +1264,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "پاک کردن جستجو هنگام تغییر پوشه",
|
||||
"description": "کلیک روی یک پوشه (صندوق ورودی، ارسالشده…) جستجوی فعال را پاک میکند و آن پوشه را نشان میدهد. وقتی خاموش باشد، جستجو باقی میماند و در پوشهٔ جدید دوباره اجرا میشود."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "درخواست تأیید خواندن",
|
||||
"description": "پیشفعال کردن تأیید خواندن"
|
||||
@@ -1324,6 +1351,12 @@
|
||||
"label": "پاسخ از آدرس دریافتی",
|
||||
"description": "ارسال با آدرس دریافت شده"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "تطبیق نشانی دریافت",
|
||||
"description": "کدام نشانیهای دریافت بهعنوان نشانی شما در نظر گرفته شوند. «فقط نشانی دقیق» تنها یکی از هویتهای پیکربندیشدهٔ شما را انتخاب میکند؛ «همان دامنه» هر نشانی دیگری در دامنههای هویت شما را نیز نام مستعار catch-all در نظر میگیرد و سرآیند فرستنده را با آن بازنویسی میکند. اگر این نشانیها فهرست توزیع هستند، نشانی دقیق را انتخاب کنید.",
|
||||
"exact": "فقط نشانی دقیق",
|
||||
"domain": "هر نشانی در دامنههای من"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "موقعیت امضا",
|
||||
"description": "محل درج امضا",
|
||||
@@ -1753,7 +1786,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "تاریخ پایان باید بعد از تاریخ شروع باشد",
|
||||
"start_in_past": "تاریخ شروع در گذشته است",
|
||||
"empty_body": "بدنه پیام خالی است - گیرندگان پاسخ خالی دریافت میکنند"
|
||||
"empty_body": "بدنه پیام خالی است - گیرندگان پاسخ خالی دریافت میکنند",
|
||||
"subject_too_long": "موضوع برای سرور بیش از حد طولانی است (حداکثر {max} بایت).",
|
||||
"body_too_long": "پیام برای سرور بیش از حد طولانی است (حداکثر {max} بایت، همراه با قالببندی)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1770,6 +1805,8 @@
|
||||
"role_trash": "زبالهدان",
|
||||
"role_junk": "هرزنامه",
|
||||
"role_archive": "بایگانی",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "هیچکدام",
|
||||
"create_folder": "ایجاد پوشه",
|
||||
"create_subfolder": "ایجاد زیرپوشه",
|
||||
@@ -1879,6 +1916,8 @@
|
||||
"title": "مخاطبین",
|
||||
"description": "وارد کردن و خروجی مخاطبین",
|
||||
"group_by_letter_label": "گروهبندی بر اساس حرف اول",
|
||||
"sort_by_last_name_label": "مرتبسازی بر اساس نام خانوادگی",
|
||||
"sort_by_last_name_description": "لیست مخاطبین را بر اساس نام خانوادگی مرتب کنید تا اعضای خانواده کنار هم نمایش داده شوند",
|
||||
"group_by_letter_description": "نمایش هدرهای الفبایی در لیست مخاطبین",
|
||||
"import_label": "وارد کردن مخاطبین",
|
||||
"import_description": "وارد کردن مخاطبین از فایل vCard (.vcf)",
|
||||
@@ -1970,6 +2009,7 @@
|
||||
"actions": "عملیاتها",
|
||||
"add_action": "افزودن عملیات",
|
||||
"stop_processing": "توقف پردازش قوانین بعدی",
|
||||
"include_spam": "پیامهای علامتخورده بهعنوان هرزنامه هم منتقل شوند",
|
||||
"attachment_type_placeholder": "مثال: pdf, doc, jpg",
|
||||
"value_placeholder_multi": "مقدار (چندتا با کاما جدا شوند)",
|
||||
"condition_fields": {
|
||||
@@ -2011,6 +2051,8 @@
|
||||
"copy_to_folder": "انتخاب پوشه",
|
||||
"forward_to": "ارسال به آدرس ایمیل",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "نگهداشتن یک نسخه",
|
||||
"forward_limit": "سقف بازارسال برای هر پیام در این سرور: {count}. بازارسالهای اضافه نادیده گرفته میشوند.",
|
||||
"reject_message": "پیام رد",
|
||||
"reject_placeholder": "ایمیل شما رد شد",
|
||||
"label_name": "نام برچسب",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "زیرپوشه جدید...",
|
||||
"new_folder": "پوشه جدید...",
|
||||
"rename": "تغییر نام...",
|
||||
"share": "اشتراکگذاری...",
|
||||
"import_email": "وارد کردن .eml/.zip...",
|
||||
"empty_folder": "خالی کردن پوشه",
|
||||
"empty_folder_generic": "خالی کردن پوشه",
|
||||
@@ -2236,7 +2279,10 @@
|
||||
"toast_error_rename": "خطای تغییر نام",
|
||||
"toast_error_delete": "خطای حذف",
|
||||
"toast_error_delete_has_children": "زیرپوشه دارد",
|
||||
"toast_error_delete_has_email": "خالی نیست"
|
||||
"toast_error_delete_has_email": "خالی نیست",
|
||||
"delete_with_contents_title": "پوشه خالی نیست",
|
||||
"delete_with_contents_message": "پوشه «{name}» هنوز شامل {count, plural, one {۱ پیام} other {# پیام}} است. پوشه و همه پیامهای آن حذف شود؟ این عمل قابل بازگشت نیست.",
|
||||
"delete_with_contents_confirm": "حذف پوشه و پیامها"
|
||||
},
|
||||
"shortcuts": {
|
||||
"title": "میانبرهای صفحه کلید",
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "تمرکز جستجو",
|
||||
"help": "نمایش میانبرها",
|
||||
"refresh": "بازنشانی ایمیلها",
|
||||
"select_all": "انتخاب همه"
|
||||
"select_all": "انتخاب همه",
|
||||
"global_search": "جستجو در همه جا"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "باز/بسته کردن رشته"
|
||||
@@ -2722,7 +2769,10 @@
|
||||
"any_month": "هر ماه",
|
||||
"has_email": "دارای ایمیل",
|
||||
"has_phone": "دارای تلفن",
|
||||
"has_photo": "دارای عکس"
|
||||
"has_photo": "دارای عکس",
|
||||
"sort_by": "مرتبسازی بر اساس",
|
||||
"sort_first_name": "نام",
|
||||
"sort_last_name": "نام خانوادگی"
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "کپی",
|
||||
"today_header": "امروز",
|
||||
"tomorrow_header": "فردا",
|
||||
"agenda_show_earlier": "نمایش رویدادهای قبلی",
|
||||
"agenda_loading": "در حال بارگذاری…",
|
||||
"agenda_range_start": "رویدادها از {date}",
|
||||
"agenda_range_end": "رویدادها تا {date}",
|
||||
"export_ics": "خروجی .ics",
|
||||
"copy_title": "کپی عنوان",
|
||||
"copy_link": "کپی لینک جلسه",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "رنگ"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "در دسترس بودن",
|
||||
"free": "در دسترس",
|
||||
"busy": "مشغول",
|
||||
"tentative": "احتمالی",
|
||||
"unknown": "کاربر این سرور نیست",
|
||||
"checking": "در حال بررسی…"
|
||||
},
|
||||
"title": "شرکتکنندگان",
|
||||
"add": "افزودن شرکتکننده",
|
||||
"organizer": "برگزارکننده",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "همه رویدادها",
|
||||
"cancel": "انصراف",
|
||||
"save": "ذخیره",
|
||||
"delete": "حذف"
|
||||
"delete": "حذف",
|
||||
"rsvp_title": "پاسخ به رویداد تکراری",
|
||||
"rsvp_description": "این یک رویداد تکراری است. پاسخ شما برای کدام رویدادها اعمال شود؟",
|
||||
"respond": "پاسخ"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "یادآورها",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "نمایش زمان رویدادها در نمای ماه تقویم",
|
||||
"show_week_numbers": "نمایش شماره هفته",
|
||||
"show_week_numbers_desc": "نمایش شماره هفته در تقویم کوچک",
|
||||
"calendar_free_scroll": "پیمایش آزاد",
|
||||
"calendar_free_scroll_desc": "پیمایش پیوسته در ماهها، هفتهها و روزها به جای یک بازه در هر بار",
|
||||
"enable_tasks": "فعال کردن وظایف",
|
||||
"enable_tasks_desc": "نمایش نمای وظایف در تقویم برای مدیریت کارها",
|
||||
"show_tasks_on_calendar": "نمایش وظایف روی تقویم",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "تأخیر ۱ ثانیه",
|
||||
"hover_preview_delay_2s": "تأخیر ۲ ثانیه",
|
||||
"hover_preview_off": "غیرفعال",
|
||||
"organizer_identity": "سازماندهی دعوتها بهعنوان",
|
||||
"organizer_identity_desc": "آدرس تقویمی که هنگام دعوت افراد به رویدادها بهعنوان برگزارکننده استفاده میشود.",
|
||||
"organizer_identity_failed": "تغییر آدرس برگزارکننده ناموفق بود",
|
||||
"show_birthday_calendar": "تقویم تولد مخاطبین",
|
||||
"show_birthday_calendar_desc": "نمایش تقویم مجازی با تولدهای مخاطبین"
|
||||
},
|
||||
@@ -2973,6 +3043,9 @@
|
||||
"alert_now": "همین الان شروع میشود",
|
||||
"alert_in_minutes": "{count} دقیقه دیگر",
|
||||
"invitation_sent": "دعوتنامهها ارسال شد",
|
||||
"invitations_denied_title": "ارسال دعوتنامهها ممکن نیست",
|
||||
"invitations_denied": "سرور از ارسال دعوتنامهها خودداری کرد: {reason}. رویداد بدون ارسال آنها ذخیره شود؟",
|
||||
"save_without_invitations": "ذخیره بدون دعوتنامه",
|
||||
"rsvp_updated": "پاسخ بهروزرسانی شد",
|
||||
"rsvp_error": "بهروزرسانی پاسخ ناموفق بود",
|
||||
"event_duplicated": "رویداد کپی شد",
|
||||
@@ -3002,7 +3075,7 @@
|
||||
"supported_formats": "پشتیبانی از فایلهای iCalendar (.ics)",
|
||||
"url_description": "آدرس فید خارجی iCalendar (.ics) را برای وارد کردن رویدادها وارد کنید.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "پشتیبانی از آدرسهای CalDAV و iCalendar (.ics)",
|
||||
"url_hint": "پشتیبانی از آدرسهای iCalendar (.ics)",
|
||||
"fetch": "دریافت",
|
||||
"invalid_url": "لطفاً یک آدرس معتبر وارد کنید",
|
||||
"url_fetch_failed": "دریافت تقویم از آدرس ناموفق بود",
|
||||
@@ -3149,6 +3222,25 @@
|
||||
"overdue": "عقبافتاده"
|
||||
}
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "کسی",
|
||||
"invited": "{name} شما را به «{title}» دعوت کرد",
|
||||
"updated": "{name} «{title}» را بهروزرسانی کرد",
|
||||
"cancelled": "{name} «{title}» را لغو کرد",
|
||||
"untitled": "رویداد بدون عنوان"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "کسی",
|
||||
"shared": "{name} {kind} «{object}» را با شما به اشتراک گذاشت",
|
||||
"changed": "{name} دسترسی شما به {kind} «{object}» را تغییر داد",
|
||||
"revoked": "{name} دسترسی شما به {kind} «{object}» را حذف کرد",
|
||||
"object": {
|
||||
"folder": "پوشه",
|
||||
"calendar": "تقویم",
|
||||
"address_book": "دفترچه آدرس",
|
||||
"files": "پوشه فایل"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "اشتراکگذاری \"{name}\"",
|
||||
"description": "دسترسی به سایر کاربران یا گروههای این سرور را فراهم کنید.",
|
||||
@@ -3187,6 +3279,8 @@
|
||||
"has_attachment": "پیوست",
|
||||
"date_after": "بعد از",
|
||||
"date_before": "قبل از",
|
||||
"size_min": "بزرگتر از (کیلوبایت)",
|
||||
"size_max": "کوچکتر از (کیلوبایت)",
|
||||
"starred": "ستارهدار",
|
||||
"unread": "خوانده نشده",
|
||||
"read": "خوانده شده",
|
||||
@@ -3228,6 +3322,10 @@
|
||||
"hint": "روی هر ایمیل در سمت چپ کلیک کنید یا تور را ببینید."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "ویرایش سند",
|
||||
"office_loading": "در حال باز کردن ویرایشگر سند…",
|
||||
"office_error": "ویرایشگر سند باز نشد.",
|
||||
"office_close": "بستن ویرایشگر",
|
||||
"open_in_new_tab": "باز کردن در تب جدید",
|
||||
"title": "فایلها",
|
||||
"search_placeholder": "جستجوی فایلها...",
|
||||
@@ -3240,6 +3338,8 @@
|
||||
"rename": "تغییر نام",
|
||||
"rename_title": "تغییر نام",
|
||||
"new_name": "نام جدید",
|
||||
"name_forbidden_chars": "سرور این نویسهها را در نامها مجاز نمیداند: {chars}",
|
||||
"name_reserved": "این نام در سرور رزرو شده است.",
|
||||
"delete": "حذف",
|
||||
"delete_confirm_title": "حذف منبع",
|
||||
"delete_confirm_message": "آیا از حذف \"{name}\" اطمینان دارید؟ این عملیات قابل بازگشت نیست.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "کپی پیوند رویداد",
|
||||
"copied": "پیوند کپی شد",
|
||||
"copy_failed": "کپی پیوند ممکن نشد"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "در حال تکمیل احراز هویت…",
|
||||
"completing_message": "لطفاً تا برقراری اتصال منتظر بمانید.",
|
||||
"received_title": "احراز هویت دریافت شد",
|
||||
"received_message": "میتوانید به برگهای که اتصال را در آن آغاز کردید بازگردید.",
|
||||
"failed_title": "احراز هویت ناموفق بود",
|
||||
"denied": "احراز هویت رد شد.",
|
||||
"failed_detail": "احراز هویت ناموفق بود: {error}",
|
||||
"missing_params": "کد احراز هویت یا حالت وجود ندارد.",
|
||||
"delivery_failed": "نتیجه احراز هویت به برگه برنامه ارسال نشد. لطفاً این پنجره را ببندید و دوباره تلاش کنید."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "جستجو در همه جا",
|
||||
"placeholder": "جستجو در ایمیل، مخاطبین، تقویم، فایلها...",
|
||||
"scope_label": "جستجو در",
|
||||
"account_label": "حساب",
|
||||
"scope_all": "همه",
|
||||
"scope_mail": "ایمیل",
|
||||
"scope_contacts": "مخاطبین",
|
||||
"scope_calendar": "تقویم",
|
||||
"scope_files": "فایلها",
|
||||
"all_accounts": "همه حسابها",
|
||||
"show_all": "نمایش همه نتایج",
|
||||
"no_results": "نتیجهای برای “{query}” یافت نشد",
|
||||
"server_hint": "کلمات کامل در سرور جستجو میشوند؛ بخشی از کلمات با موارد بارگذاریشده مطابقت دارد",
|
||||
"searching": "در حال جستجو...",
|
||||
"account_error": "جستجو در {account} ممکن نشد",
|
||||
"recent_searches": "جستجوهای اخیر",
|
||||
"recurring": "تکرارشونده",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "در ایمیل، مخاطبین، رویدادها و فایلهای همه حسابها جستجو کنید. امتحان کنید: from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "تخت بر اساس تاریخ",
|
||||
"grouped": "گروهبندیشده",
|
||||
"load_more": "بارگذاری بیشتر",
|
||||
"preview_empty": "برای پیشنمایش یک نتیجه انتخاب کنید",
|
||||
"open_full": "باز کردن",
|
||||
"view_label": "نما"
|
||||
}
|
||||
}
|
||||
|
||||
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "et {count} autres",
|
||||
"draft": "Brouillon",
|
||||
"starred": "Favori",
|
||||
"pinned": "Épinglé",
|
||||
"replied": "Répondu",
|
||||
"forwarded": "Transféré",
|
||||
"has_attachment": "Avec pièce jointe",
|
||||
"conversations_count": "{count} sur {total} conversations",
|
||||
"conversations_count_plus": "{count}+ conversations",
|
||||
"conversations_count_simple": "{count} conversations",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Transférer",
|
||||
"delete": "Supprimer",
|
||||
"archive": "Archiver",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Marquer comme favori",
|
||||
"unstar": "Retirer des favoris",
|
||||
"mark_unread": "Marquer comme non lu",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Déplacer",
|
||||
"print": "Imprimer",
|
||||
"view_source": "Voir la source",
|
||||
|
||||
"fullscreen": "Plein écran",
|
||||
|
||||
"exit_fullscreen": "Quitter le plein écran",
|
||||
"export_email": "Exporter en .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Transférer en pièce jointe",
|
||||
"import_email": "Importer .eml ou .zip",
|
||||
"keyboard_shortcuts": "Raccourcis clavier (?)",
|
||||
"email_source": "Source de l'email",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Accusé de lecture demandé (cliquez pour désactiver)",
|
||||
"read_receipt_off": "Demander un accusé de lecture",
|
||||
"dsn_on": "Notification de remise demandée (cliquer pour désactiver)",
|
||||
"dsn_off": "Demander une notification de remise",
|
||||
"format_plain_text": "Passer en texte brut",
|
||||
"format_rich_text": "Passer en texte enrichi (HTML)",
|
||||
"require_tls_on": "Remise chiffrée exigée (cliquer pour désactiver)",
|
||||
"require_tls_off": "Exiger une remise chiffrée (TLS)",
|
||||
"new_message": "Nouveau message",
|
||||
"signature_edit_hint": "Double-cliquez pour modifier la signature",
|
||||
"reply": "Répondre",
|
||||
@@ -631,11 +640,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Remplacer",
|
||||
"toggle_on": "Annuler le remplacement",
|
||||
"toggle_tooltip": "Modifiez librement le nom et l'adresse d'expéditeur. Le courrier est toujours envoyé via votre identité - seul l'en-tête De visible change.",
|
||||
"toggle_tooltip": "Modifiez librement le nom et l'adresse d'expéditeur. Le courrier est toujours envoyé via votre identité.",
|
||||
"name_label": "Nom de l'expéditeur",
|
||||
"name_placeholder": "Nom",
|
||||
"email_label": "Adresse e-mail de l'expéditeur",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "Si votre serveur n'accepte pas cette adresse comme expéditeur d'enveloppe, {identity} est utilisée à la place, et les destinataires peuvent la voir dans l'en-tête Return-Path."
|
||||
},
|
||||
"use_template": "Modèle",
|
||||
"save_as_template": "Enregistrer comme modèle",
|
||||
@@ -648,6 +658,8 @@
|
||||
"upload_progress": "Envoi {uploaded} / {total}",
|
||||
"upload_cancel": "Annuler l'envoi",
|
||||
"upload_failed": "Échec du téléversement de {filename}",
|
||||
"attachment_too_large": "« {name} » dépasse la taille autorisée par le serveur ({max} par fichier)",
|
||||
"attachments_total_too_large": "Les pièces jointes dépasseraient la limite du serveur de {max} par message",
|
||||
"send_failed": "Échec de l'envoi de l'e-mail",
|
||||
"smime_sign_on": "Signature S/MIME activée",
|
||||
"smime_sign_off": "Signature S/MIME désactivée",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Supprimer la colonne",
|
||||
"toggle_header_row": "Basculer la ligne d'en-tête",
|
||||
"delete_table": "Supprimer le tableau",
|
||||
"pick_size": "Choisir la taille"
|
||||
"pick_size": "Choisir la taille",
|
||||
"font_size": "Taille de police",
|
||||
"font_size_default": "Par défaut",
|
||||
"background_color": "Couleur d'arrière-plan",
|
||||
"remove_background_color": "Supprimer la couleur d'arrière-plan"
|
||||
},
|
||||
"send_filing_warning": "Envoyé - mais le nettoyage après envoi a échoué, un ancien brouillon peut subsister."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "Email envoyé avec succès",
|
||||
"email_deleted": "Email supprimé",
|
||||
"email_archived": "Email archivé",
|
||||
"emails_deleted": "{count, plural, one {# e-mail supprimé} other {# e-mails supprimés}}",
|
||||
"emails_archived": "{count, plural, one {# e-mail archivé} other {# e-mails archivés}}",
|
||||
"error_archiving": "Impossible d'archiver l'e-mail",
|
||||
"error_updating": "Impossible de mettre à jour l'e-mail",
|
||||
"email_starred": "Email ajouté aux favoris",
|
||||
"email_unstarred": "Email retiré des favoris",
|
||||
"email_marked_read": "Email marqué comme lu",
|
||||
@@ -1143,7 +1163,9 @@
|
||||
"enabled": "Notifications par e-mail",
|
||||
"enabled_desc": "Afficher des notifications à l'arrivée de nouveaux e-mails",
|
||||
"sound": "Son de notification",
|
||||
"sound_desc": "Jouer un son à l'arrivée de nouveaux e-mails"
|
||||
"sound_desc": "Jouer un son à l'arrivée de nouveaux e-mails",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Notifications de calendrier",
|
||||
@@ -1172,6 +1194,7 @@
|
||||
"status_busy": "Traitement en cours…",
|
||||
"status_inactive": "Non activé sur cet appareil",
|
||||
"status_unsupported": "Ce navigateur ne prend pas en charge Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"title": "Notifications en arrière-plan",
|
||||
"devices_title": "Appareils enregistrés",
|
||||
"devices_desc": "Tous les appareils enregistrés pour recevoir les notifications en arrière-plan de ce compte. Révoquer un appareil l’empêche immédiatement d’en recevoir.",
|
||||
@@ -1238,6 +1261,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Effacer la recherche lors du changement de dossier",
|
||||
"description": "Cliquer sur un dossier (Boîte de réception, Envoyés…) efface la recherche active et affiche ce dossier. Désactivé, la recherche reste appliquée et est relancée dans le dossier choisi."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Demander un accusé de lecture par défaut",
|
||||
"description": "Activer au préalable la demande d''accusé de lecture lors de la rédaction d''un nouveau message."
|
||||
@@ -1316,6 +1343,12 @@
|
||||
"label": "Sélection automatique de l'adresse de réponse",
|
||||
"description": "Lors d'une réponse, bascule automatiquement l'adresse d'expédition vers l'identité qui a reçu le message d'origine"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Correspondance de l'adresse de réception",
|
||||
"description": "Quelles adresses de réception comptent comme les vôtres. « Adresse exacte uniquement » ne choisit qu'une de vos identités configurées ; « même domaine » traite aussi toute autre adresse de vos domaines d'identité comme un alias catch-all et y réécrit l'en-tête De. Choisissez l'adresse exacte si ces adresses sont des listes de diffusion.",
|
||||
"exact": "Adresse exacte uniquement",
|
||||
"domain": "Toute adresse de mes domaines"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Position de la signature",
|
||||
"description": "Où insérer votre signature dans les réponses et les transferts. Au-dessus du texte cité, elle se lit naturellement comme la conclusion de la réponse ; en dessous, elle garde le message d'origine contigu.",
|
||||
@@ -1746,7 +1779,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "La date de fin doit être postérieure à la date de début",
|
||||
"start_in_past": "La date de début est dans le passé",
|
||||
"empty_body": "Le corps du message est vide - les destinataires recevront une réponse vide"
|
||||
"empty_body": "Le corps du message est vide - les destinataires recevront une réponse vide",
|
||||
"subject_too_long": "L'objet est trop long pour le serveur ({max} octets au maximum).",
|
||||
"body_too_long": "Le message est trop long pour le serveur ({max} octets au maximum, mise en forme comprise)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1763,6 +1798,8 @@
|
||||
"role_trash": "Corbeille",
|
||||
"role_junk": "Spam / Indésirables",
|
||||
"role_archive": "Archives",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Aucun",
|
||||
"create_folder": "Créer un dossier",
|
||||
"create_subfolder": "Créer un sous-dossier",
|
||||
@@ -1882,7 +1919,9 @@
|
||||
"categories_description": "Renommer les catégories de contacts",
|
||||
"no_categories": "Aucune catégorie trouvée",
|
||||
"group_by_letter_description": "Afficher des en-têtes alphabétiques dans la liste de contacts",
|
||||
"group_by_letter_label": "Grouper par première lettre"
|
||||
"group_by_letter_label": "Grouper par première lettre",
|
||||
"sort_by_last_name_label": "Trier par nom de famille",
|
||||
"sort_by_last_name_description": "Classer la liste des contacts par nom de famille pour regrouper les membres d'une même famille"
|
||||
},
|
||||
"downloads": {
|
||||
"title": "Téléchargements",
|
||||
@@ -1963,6 +2002,7 @@
|
||||
"actions": "Actions",
|
||||
"add_action": "Ajouter une action",
|
||||
"stop_processing": "Arrêter le traitement des règles suivantes",
|
||||
"include_spam": "Déplacer aussi les messages marqués comme spam",
|
||||
"attachment_type_placeholder": "p. ex. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Valeur (plusieurs séparées par des virgules)",
|
||||
"condition_fields": {
|
||||
@@ -2004,6 +2044,8 @@
|
||||
"copy_to_folder": "Sélectionner le dossier",
|
||||
"forward_to": "Transférer à l'adresse e-mail",
|
||||
"forward_placeholder": "email@exemple.com",
|
||||
"forward_keep_copy": "Garder une copie",
|
||||
"forward_limit": "Limite de transferts par message sur ce serveur : {count}. Les transferts en trop sont ignorés.",
|
||||
"reject_message": "Message de rejet",
|
||||
"reject_placeholder": "Votre e-mail a été rejeté",
|
||||
"label_name": "Nom de l'étiquette",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Nouveau sous-dossier...",
|
||||
"new_folder": "Nouveau dossier...",
|
||||
"rename": "Renommer...",
|
||||
"share": "Partager...",
|
||||
"import_email": "Importer .eml ou .zip...",
|
||||
"empty_folder": "Vider le dossier",
|
||||
"empty_folder_generic": "Vider le dossier",
|
||||
@@ -2234,6 +2277,9 @@
|
||||
"toast_error_delete": "Échec de la suppression du dossier",
|
||||
"toast_error_delete_has_children": "Le dossier contient des sous-dossiers. Supprimez-les d'abord.",
|
||||
"toast_error_delete_has_email": "Le dossier n'est pas vide. Videz-le d'abord.",
|
||||
"delete_with_contents_title": "Le dossier n'est pas vide",
|
||||
"delete_with_contents_message": "Le dossier « {name} » contient encore {count, plural, one {1 message} other {# messages}}. Supprimer le dossier et tous ses messages ? Cette action est irréversible.",
|
||||
"delete_with_contents_confirm": "Supprimer le dossier et les messages",
|
||||
"placeholder_folder_name": "Nom du dossier",
|
||||
"create": "Créer",
|
||||
"rename_confirm": "Renommer"
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Rechercher",
|
||||
"help": "Afficher les raccourcis",
|
||||
"refresh": "Actualiser les emails",
|
||||
"select_all": "Tout sélectionner"
|
||||
"select_all": "Tout sélectionner",
|
||||
"global_search": "Tout rechercher"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Développer/réduire la conversation"
|
||||
@@ -2721,7 +2768,10 @@
|
||||
"any_month": "Tous les mois",
|
||||
"has_email": "Avec e-mail",
|
||||
"has_phone": "Avec téléphone",
|
||||
"has_photo": "Avec photo"
|
||||
"has_photo": "Avec photo",
|
||||
"sort_by": "Trier par",
|
||||
"sort_first_name": "Prénom",
|
||||
"sort_last_name": "Nom de famille"
|
||||
},
|
||||
"open_categories": "Ouvrir les catégories"
|
||||
},
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "Dupliquer",
|
||||
"today_header": "Aujourd'hui",
|
||||
"tomorrow_header": "Demain",
|
||||
"agenda_show_earlier": "Afficher les événements précédents",
|
||||
"agenda_loading": "Chargement…",
|
||||
"agenda_range_start": "Événements à partir du {date}",
|
||||
"agenda_range_end": "Événements jusqu'au {date}",
|
||||
"export_ics": "Exporter en .ics",
|
||||
"copy_title": "Copier le titre",
|
||||
"copy_link": "Copier le lien de réunion",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "Couleur"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Disponibilité",
|
||||
"free": "Disponible",
|
||||
"busy": "Occupé",
|
||||
"tentative": "Provisoire",
|
||||
"unknown": "Pas un utilisateur de ce serveur",
|
||||
"checking": "Vérification…"
|
||||
},
|
||||
"title": "Participants",
|
||||
"add": "Ajouter un participant",
|
||||
"organizer": "Organisateur",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Tous les événements",
|
||||
"cancel": "Annuler",
|
||||
"save": "Enregistrer",
|
||||
"delete": "Supprimer"
|
||||
"delete": "Supprimer",
|
||||
"rsvp_title": "Répondre à l'événement récurrent",
|
||||
"rsvp_description": "Ceci est un événement récurrent. À quels événements votre réponse s'applique-t-elle ?",
|
||||
"respond": "Répondre"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Rappel",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Afficher les heures des événements dans la vue mensuelle du calendrier",
|
||||
"show_week_numbers": "Afficher les numéros de semaine",
|
||||
"show_week_numbers_desc": "Afficher les numéros de semaine dans le mini-calendrier",
|
||||
"calendar_free_scroll": "Défilement libre",
|
||||
"calendar_free_scroll_desc": "Faire défiler les mois, semaines et jours en continu au lieu d'une période à la fois",
|
||||
"enable_tasks": "Activer les tâches",
|
||||
"enable_tasks_desc": "Afficher une vue des tâches dans le calendrier pour gérer les choses à faire",
|
||||
"show_tasks_on_calendar": "Afficher les tâches dans le calendrier",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "Délai de 1 seconde",
|
||||
"hover_preview_delay_2s": "Délai de 2 secondes",
|
||||
"hover_preview_off": "Désactivé",
|
||||
"organizer_identity": "Organiser les invitations en tant que",
|
||||
"organizer_identity_desc": "L'adresse d'agenda utilisée comme organisateur lorsque vous invitez des personnes à des événements.",
|
||||
"organizer_identity_failed": "Impossible de modifier l'adresse de l'organisateur",
|
||||
"show_birthday_calendar": "Calendrier des anniversaires",
|
||||
"show_birthday_calendar_desc": "Afficher un calendrier virtuel avec les anniversaires de vos contacts"
|
||||
},
|
||||
@@ -2973,6 +3043,9 @@
|
||||
"alert_now": "Commence maintenant",
|
||||
"alert_in_minutes": "Dans {count} min",
|
||||
"invitation_sent": "Invitations envoyées",
|
||||
"invitations_denied_title": "Impossible d'envoyer les invitations",
|
||||
"invitations_denied": "Le serveur a refusé d'envoyer les invitations : {reason}. Enregistrer l'événement sans les envoyer ?",
|
||||
"save_without_invitations": "Enregistrer sans invitations",
|
||||
"rsvp_updated": "Réponse mise à jour",
|
||||
"rsvp_error": "Échec de la mise à jour de la réponse",
|
||||
"event_duplicated": "Événement dupliqué",
|
||||
@@ -3001,7 +3074,7 @@
|
||||
"supported_formats": "Fichiers iCalendar (.ics) supportés",
|
||||
"url_description": "Entrez l'URL d'un flux iCalendar (.ics) externe pour importer des événements.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "Prend en charge les URLs CalDAV et iCalendar (.ics)",
|
||||
"url_hint": "Prend en charge les URLs iCalendar (.ics)",
|
||||
"fetch": "Récupérer",
|
||||
"invalid_url": "Veuillez entrer une URL valide",
|
||||
"url_fetch_failed": "Impossible de récupérer le calendrier depuis l'URL",
|
||||
@@ -3164,6 +3237,8 @@
|
||||
"has_attachment": "Pièces jointes",
|
||||
"date_after": "Après le",
|
||||
"date_before": "Avant le",
|
||||
"size_min": "Plus grand que (Ko)",
|
||||
"size_max": "Plus petit que (Ko)",
|
||||
"starred": "Suivis",
|
||||
"unread": "Non lus",
|
||||
"read": "Lus",
|
||||
@@ -3205,6 +3280,10 @@
|
||||
"hint": "Cliquez sur un e-mail à gauche pour commencer, ou lancez la visite."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Modifier le document",
|
||||
"office_loading": "Ouverture de l'éditeur de documents…",
|
||||
"office_error": "L'éditeur de documents n'a pas pu être ouvert.",
|
||||
"office_close": "Fermer l'éditeur",
|
||||
"open_in_new_tab": "Ouvrir dans un nouvel onglet",
|
||||
"title": "Fichiers",
|
||||
"search_placeholder": "Rechercher des fichiers...",
|
||||
@@ -3217,6 +3296,8 @@
|
||||
"rename": "Renommer",
|
||||
"rename_title": "Renommer",
|
||||
"new_name": "Nouveau nom",
|
||||
"name_forbidden_chars": "Le serveur n'autorise pas ces caractères dans les noms : {chars}",
|
||||
"name_reserved": "Ce nom est réservé sur le serveur.",
|
||||
"delete": "Supprimer",
|
||||
"delete_confirm_title": "Supprimer la ressource",
|
||||
"delete_confirm_message": "Êtes-vous sûr de vouloir supprimer \"{name}\" ? Cette action est irréversible.",
|
||||
@@ -3451,6 +3532,25 @@
|
||||
"unified_mailbox": {
|
||||
"search_unavailable": "La recherche n'est pas disponible dans la vue unifiée"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Quelqu'un",
|
||||
"invited": "{name} vous a invité à « {title} »",
|
||||
"updated": "{name} a mis à jour « {title} »",
|
||||
"cancelled": "{name} a annulé « {title} »",
|
||||
"untitled": "Événement sans titre"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Quelqu'un",
|
||||
"shared": "{name} a partagé {kind} « {object} » avec vous",
|
||||
"changed": "{name} a modifié votre accès à {kind} « {object} »",
|
||||
"revoked": "{name} a retiré votre accès à {kind} « {object} »",
|
||||
"object": {
|
||||
"folder": "le dossier",
|
||||
"calendar": "l'agenda",
|
||||
"address_book": "le carnet d'adresses",
|
||||
"files": "le dossier de fichiers"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Partager « {name} »",
|
||||
"description": "Accordez l'accès à d'autres utilisateurs ou groupes de ce serveur. Les modifications sont immédiates.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Copier le lien de l'événement",
|
||||
"copied": "Lien copié",
|
||||
"copy_failed": "Impossible de copier le lien"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Finalisation de l'autorisation…",
|
||||
"completing_message": "Veuillez patienter pendant l'établissement de la connexion.",
|
||||
"received_title": "Autorisation reçue",
|
||||
"received_message": "Vous pouvez revenir à l'onglet où vous avez commencé la connexion.",
|
||||
"failed_title": "Échec de l'autorisation",
|
||||
"denied": "Lautorisation a été refusée.",
|
||||
"failed_detail": "Échec de lautorisation : {error}",
|
||||
"missing_params": "Code dautorisation ou état manquant.",
|
||||
"delivery_failed": "Le résultat de lautorisation na pas pu être transmis à longlet de lapplication. Fermez cette fenêtre et réessayez."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Tout rechercher",
|
||||
"placeholder": "Rechercher courriels, contacts, calendrier, fichiers...",
|
||||
"scope_label": "Rechercher dans",
|
||||
"account_label": "Compte",
|
||||
"scope_all": "Tout",
|
||||
"scope_mail": "Messagerie",
|
||||
"scope_contacts": "Contacts",
|
||||
"scope_calendar": "Calendrier",
|
||||
"scope_files": "Fichiers",
|
||||
"all_accounts": "Tous les comptes",
|
||||
"show_all": "Afficher tous les résultats",
|
||||
"no_results": "Aucun résultat pour « {query} »",
|
||||
"server_hint": "Les mots complets interrogent le serveur ; les mots partiels correspondent aux éléments déjà chargés",
|
||||
"searching": "Recherche...",
|
||||
"account_error": "Impossible de rechercher dans {account}",
|
||||
"recent_searches": "Recherches récentes",
|
||||
"recurring": "Récurrent",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Recherchez courriels, contacts, événements et fichiers sur tous les comptes. Essayez from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "À plat par date",
|
||||
"grouped": "Groupé",
|
||||
"load_more": "Charger plus",
|
||||
"preview_empty": "Sélectionnez un résultat pour l'aperçu",
|
||||
"open_full": "Ouvrir",
|
||||
"view_label": "Affichage"
|
||||
}
|
||||
}
|
||||
|
||||
+153
-13
@@ -190,6 +190,10 @@
|
||||
"and_others": "ו-{count}אחרים",
|
||||
"draft": "טְיוּטָה",
|
||||
"starred": "מסומן בכוכב",
|
||||
"pinned": "מוצמד",
|
||||
"replied": "נענה",
|
||||
"forwarded": "הועבר",
|
||||
"has_attachment": "כולל קובץ מצורף",
|
||||
"conversations_count": "{count}מתוך שיחות{total}",
|
||||
"conversations_count_plus": "{count}+ שיחות",
|
||||
"conversations_count_simple": "שיחות{count}",
|
||||
@@ -244,18 +248,17 @@
|
||||
"forward": "העבר",
|
||||
"delete": "מחיקה",
|
||||
"archive": "ארכיון",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "כוכב",
|
||||
"unstar": "הסר כוכב",
|
||||
"mark_unread": "סמן כלא נקרא",
|
||||
"mark_read": "סמן כנקרא",
|
||||
"print": "הדפס",
|
||||
"view_source": "צפה במקור",
|
||||
|
||||
"fullscreen": "מסך מלא",
|
||||
|
||||
"exit_fullscreen": "יציאה ממסך מלא",
|
||||
"export_email": "ייצא כ-.eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "העבר כקובץ מצורף",
|
||||
"import_email": "ייבוא .eml",
|
||||
"keyboard_shortcuts": "קיצורי מקשים (?)",
|
||||
"email_source": "מקור דוא\"ל",
|
||||
@@ -600,6 +603,8 @@
|
||||
"upload_progress": "מעלה את{uploaded}/{total}",
|
||||
"upload_cancel": "בטל העלאה",
|
||||
"upload_failed": "העלאת{filename}נכשלה",
|
||||
"attachment_too_large": "\"{name}\" גדול ממה שהשרת מאפשר ({max} לקובץ)",
|
||||
"attachments_total_too_large": "הקבצים המצורפים יחרגו ממגבלת השרת של {max} להודעה",
|
||||
"drop_files": "שחרר קבצים לצירוף",
|
||||
"show_less": "הצג פחות",
|
||||
"send_failed": "שליחת האימייל נכשלה",
|
||||
@@ -618,6 +623,12 @@
|
||||
"smime_passphrase_placeholder": "ביטוי סיסמה",
|
||||
"read_receipt_on": "קבלת דואר מבוקשת (לחץ כדי להשבית)",
|
||||
"read_receipt_off": "בקש קבלת דואר",
|
||||
"dsn_on": "התבקש אישור מסירה (לחצו לביטול)",
|
||||
"dsn_off": "בקשת אישור מסירה",
|
||||
"format_plain_text": "מעבר לטקסט רגיל",
|
||||
"format_rich_text": "מעבר לטקסט עשיר (HTML)",
|
||||
"require_tls_on": "נדרשת מסירה מוצפנת (לחצו לביטול)",
|
||||
"require_tls_off": "דרישת מסירה מוצפנת (TLS)",
|
||||
"attach_photos": "תמונות וסרטונים",
|
||||
"attach_files": "קבצים",
|
||||
"sending": "שולח…",
|
||||
@@ -626,11 +637,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "עקוף",
|
||||
"toggle_on": "בטל עקיפה",
|
||||
"toggle_tooltip": "ערוך את שם ההַשגחה וכתובת בחופשיות. הדוא״ל עדיין נשלח דרך הזהות שלך - רק כותרת ההַשגחה הנראית משתנה.",
|
||||
"toggle_tooltip": "ערוך בחופשיות את שם וכתובת השולח. הדוא״ל עדיין נשלח דרך הזהות שלך.",
|
||||
"name_label": "שם ממנו",
|
||||
"name_placeholder": "שם",
|
||||
"email_label": "כתובת דוא״ל ממנו",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "אם השרת שלך לא מקבל את הכתובת הזו כשולח המעטפה, ייעשה שימוש ב-{identity} במקומה, והנמענים יכולים לראות אותה בכותרת Return-Path."
|
||||
},
|
||||
"forgot_attachment": {
|
||||
"title": "נשכחת קובץ מצורף?",
|
||||
@@ -691,7 +703,11 @@
|
||||
"delete_column": "מחיקת עמודה",
|
||||
"toggle_header_row": "החלפת שורת כותרת",
|
||||
"delete_table": "מחיקת טבלה",
|
||||
"pick_size": "בחירת גודל"
|
||||
"pick_size": "בחירת גודל",
|
||||
"font_size": "גודל גופן",
|
||||
"font_size_default": "ברירת מחדל",
|
||||
"background_color": "צבע רקע",
|
||||
"remove_background_color": "הסר צבע רקע"
|
||||
},
|
||||
"send_filing_warning": "נשלח - אך הניקוי שלאחר השליחה נכשל, ייתכן שתישאר טיוטה ישנה."
|
||||
},
|
||||
@@ -730,6 +746,10 @@
|
||||
"email_sent": "האימייל נשלח בהצלחה",
|
||||
"email_deleted": "האימייל נמחק",
|
||||
"email_archived": "האימייל הועבר לארכיון",
|
||||
"emails_deleted": "{count, plural, one {הודעה אחת נמחקה} other {# הודעות נמחקו}}",
|
||||
"emails_archived": "{count, plural, one {הודעה אחת הועברה לארכיון} other {# הודעות הועברו לארכיון}}",
|
||||
"error_archiving": "העברת ההודעה לארכיון נכשלה",
|
||||
"error_updating": "עדכון ההודעה נכשל",
|
||||
"email_starred": "אימייל מסומן בכוכב",
|
||||
"email_unstarred": "דוא\"ל בוטל בכוכב",
|
||||
"email_marked_read": "אימייל סומן כנקראה",
|
||||
@@ -1108,7 +1128,9 @@
|
||||
"enabled": "התראות באימייל",
|
||||
"enabled_desc": "הצג התראות כשמגיעות אימיילים חדשים",
|
||||
"sound": "צליל התראה",
|
||||
"sound_desc": "הפעל התראת שמע כשמגיעות הודעות דוא\"ל חדשות"
|
||||
"sound_desc": "הפעל התראת שמע כשמגיעות הודעות דוא\"ל חדשות",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "הודעות לוח שנה",
|
||||
@@ -1130,6 +1152,7 @@
|
||||
"status_active": "פעיל בהתקן זה",
|
||||
"status_inactive": "לא מופעל בהתקן זה",
|
||||
"status_unsupported": "דפדפן זה אינו תומך בדחיפה ברשת",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"status_busy": "עובד…",
|
||||
"enable": "הפעל",
|
||||
"reenable": "רשום מחדש",
|
||||
@@ -1203,6 +1226,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "נקה את החיפוש בעת מעבר בין תיקיות",
|
||||
"description": "לחיצה על תיקייה (דואר נכנס, נשלח…) מנקה את החיפוש הפעיל ומציגה את התיקייה. כשהאפשרות כבויה, החיפוש נשאר ומופעל מחדש בתיקייה שאליה עברת."
|
||||
},
|
||||
"title": "התנהגות אימייל",
|
||||
"description": "קבע את אופן הטיפול בהודעות דוא\"ל",
|
||||
"mark_read": {
|
||||
@@ -1275,6 +1302,12 @@
|
||||
"label": "בחירה אוטומטית בכתובת תשובה",
|
||||
"description": "בעת תשובה, העבר אוטומטית את הכתובת מאת לזהות שקיבלה את ההודעה במקור"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "התאמת כתובת הקבלה",
|
||||
"description": "אילו כתובות קבלה נחשבות לשלך. „כתובת מדויקת בלבד“ בוחרת רק אחת מהזהויות שהגדרת; „אותו דומיין“ מתייחסת גם לכל כתובת אחרת בדומייני הזהויות שלך ככינוי catch-all ומשכתבת אליה את כותרת השולח. בחר בכתובת מדויקת אם כתובות אלה הן רשימות תפוצה.",
|
||||
"exact": "כתובת מדויקת בלבד",
|
||||
"domain": "כל כתובת בדומיינים שלי"
|
||||
},
|
||||
"attachment_click_action": {
|
||||
"label": "קובץ מצורף לחץ על פעולה",
|
||||
"description": "בחר אם לחיצה על קובץ מצורף תציג אותו בתצוגה מקדימה או תוריד אותו מיד",
|
||||
@@ -1711,7 +1744,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "תאריך הסיום חייב להיות אחרי תאריך ההתחלה",
|
||||
"start_in_past": "תאריך ההתחלה הוא בעבר",
|
||||
"empty_body": "גוף ההודעה ריק - הנמענים יקבלו תשובה ריקה"
|
||||
"empty_body": "גוף ההודעה ריק - הנמענים יקבלו תשובה ריקה",
|
||||
"subject_too_long": "הנושא ארוך מדי עבור השרת (לכל היותר {max} בתים).",
|
||||
"body_too_long": "ההודעה ארוכה מדי עבור השרת (לכל היותר {max} בתים, כולל עיצוב)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1727,6 +1762,8 @@
|
||||
"role_trash": "אַשׁפָּה",
|
||||
"role_junk": "ספאם / זבל",
|
||||
"role_archive": "ארכיון",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "אין",
|
||||
"create_folder": "צור תיקיה",
|
||||
"create_subfolder": "צור תיקיית משנה",
|
||||
@@ -1848,6 +1885,8 @@
|
||||
"categories_description": "שנה שם של קטגוריות אנשי קשר",
|
||||
"no_categories": "לא נמצאו קטגוריות",
|
||||
"group_by_letter_label": "קבוצה לפי אות ראשונה",
|
||||
"sort_by_last_name_label": "מיון לפי שם משפחה",
|
||||
"sort_by_last_name_description": "סידור רשימת אנשי הקשר לפי שם משפחה כך שבני אותה משפחה יופיעו יחד",
|
||||
"group_by_letter_description": "הצג כותרות חלקים אלפביתיים ברשימת אנשי הקשר"
|
||||
},
|
||||
"filters": {
|
||||
@@ -1885,6 +1924,7 @@
|
||||
"actions": "פעולות",
|
||||
"add_action": "הוסף פעולה",
|
||||
"stop_processing": "הפסק לעבד את הכללים הבאים",
|
||||
"include_spam": "להעביר גם הודעות שסומנו כדואר זבל",
|
||||
"condition_fields": {
|
||||
"from": "מ",
|
||||
"to": "ל",
|
||||
@@ -1924,6 +1964,8 @@
|
||||
"copy_to_folder": "בחר תיקייה",
|
||||
"forward_to": "העבר לכתובת המייל",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "שמור עותק",
|
||||
"forward_limit": "מגבלת העברות לכל הודעה בשרת זה: {count}. העברות נוספות מדולגות.",
|
||||
"reject_message": "הודעת דחייה",
|
||||
"reject_placeholder": "האימייל שלך נדחה",
|
||||
"label_name": "שם התווית",
|
||||
@@ -2198,7 +2240,8 @@
|
||||
"search": "התמקד בחיפוש",
|
||||
"help": "הצג קיצורי דרך",
|
||||
"refresh": "רענן מיילים",
|
||||
"select_all": "בחר הכל"
|
||||
"select_all": "בחר הכל",
|
||||
"global_search": "חיפוש בכל מקום"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "הרחב/כווץ את השרשור"
|
||||
@@ -2650,7 +2693,10 @@
|
||||
"any_month": "כל חודש",
|
||||
"has_email": "יש דוא״ל",
|
||||
"has_phone": "יש טלפון",
|
||||
"has_photo": "יש תמונה"
|
||||
"has_photo": "יש תמונה",
|
||||
"sort_by": "מיון לפי",
|
||||
"sort_first_name": "שם פרטי",
|
||||
"sort_last_name": "שם משפחה"
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
@@ -2686,6 +2732,10 @@
|
||||
"duplicate": "שכפול",
|
||||
"today_header": "היום",
|
||||
"tomorrow_header": "מחר",
|
||||
"agenda_show_earlier": "הצגת אירועים קודמים",
|
||||
"agenda_loading": "טוען…",
|
||||
"agenda_range_start": "אירועים מ-{date}",
|
||||
"agenda_range_end": "אירועים עד {date}",
|
||||
"export_ics": "יצא כ .ics",
|
||||
"copy_title": "עותק כותרת",
|
||||
"copy_link": "עותק קישור פגישה",
|
||||
@@ -2721,6 +2771,14 @@
|
||||
"color": "צבע"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "זמינות",
|
||||
"free": "פנוי",
|
||||
"busy": "עסוק",
|
||||
"tentative": "טנטטיבי",
|
||||
"unknown": "לא משתמש בשרת זה",
|
||||
"checking": "בודק…"
|
||||
},
|
||||
"title": "משתתפים",
|
||||
"add": "הוסף משתתף",
|
||||
"organizer": "מארגן",
|
||||
@@ -2795,7 +2853,10 @@
|
||||
"all_events": "כל האירועים",
|
||||
"cancel": "לְבַטֵל",
|
||||
"save": "שמור",
|
||||
"delete": "לִמְחוֹק"
|
||||
"delete": "לִמְחוֹק",
|
||||
"rsvp_title": "השב לאירוע חוזר",
|
||||
"rsvp_description": "זהו אירוע שחוזר על עצמו. על אילו אירועים חלה התשובה שלך?",
|
||||
"respond": "השב"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "תזכורת",
|
||||
@@ -2833,6 +2894,8 @@
|
||||
"show_time_in_month_view_desc": "הצג זמני אירועים בתצוגת לוח השנה של החודש",
|
||||
"show_week_numbers": "הצג מספרי שבוע",
|
||||
"show_week_numbers_desc": "הצג מספרי שבוע במיני-לוח שנה",
|
||||
"calendar_free_scroll": "גלילה חופשית",
|
||||
"calendar_free_scroll_desc": "גלילה רציפה בין חודשים, שבועות וימים במקום תקופה אחת בכל פעם",
|
||||
"enable_tasks": "אפשר משימות",
|
||||
"enable_tasks_desc": "הצג תצוגת משימות בלוח השנה לניהול מטלות",
|
||||
"show_tasks_on_calendar": "הצג משימות בלוח השנה",
|
||||
@@ -2844,6 +2907,9 @@
|
||||
"hover_preview_delay_1s": "עיכוב של שנייה",
|
||||
"hover_preview_delay_2s": "עיכוב של 2 שניות",
|
||||
"hover_preview_off": "מושבת",
|
||||
"organizer_identity": "ארגון הזמנות בתור",
|
||||
"organizer_identity_desc": "כתובת היומן שתשמש כמארגן כשמזמינים אנשים לאירועים.",
|
||||
"organizer_identity_failed": "שינוי כתובת המארגן נכשל",
|
||||
"show_birthday_calendar": "צור קשר עם יומן יום הולדת",
|
||||
"show_birthday_calendar_desc": "הצג לוח שנה וירטואלי עם ימי הולדת מאנשי הקשר שלך"
|
||||
},
|
||||
@@ -2875,6 +2941,9 @@
|
||||
"alert_now": "מתחיל עכשיו",
|
||||
"alert_in_minutes": "ב-{count}דקות",
|
||||
"invitation_sent": "נשלחו הזמנות",
|
||||
"invitations_denied_title": "לא ניתן לשלוח את ההזמנות",
|
||||
"invitations_denied": "השרת סירב לשלוח את ההזמנות: {reason}. לשמור את האירוע בלי לשלוח אותן?",
|
||||
"save_without_invitations": "שמור בלי הזמנות",
|
||||
"rsvp_updated": "התגובה עודכנה",
|
||||
"rsvp_error": "עדכון התגובה נכשל",
|
||||
"event_duplicated": "האירוע שוכפל",
|
||||
@@ -2903,7 +2972,7 @@
|
||||
"supported_formats": "תומך בקבצי iCalendar (.ics).",
|
||||
"url_description": "הזן את כתובת האתר של עדכון iCalendar (.ics) חיצוני כדי לייבא אירועים.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "תומך בכתובות URL של CalDAV ו-iCalendar (.ics).",
|
||||
"url_hint": "תומך בכתובות URL של iCalendar (.ics).",
|
||||
"fetch": "אחזר",
|
||||
"invalid_url": "אנא הזן כתובת אתר חוקית",
|
||||
"url_fetch_failed": "אחזור היומן מ-URL נכשל",
|
||||
@@ -3092,6 +3161,8 @@
|
||||
"has_attachment": "קבצים מצורפים",
|
||||
"date_after": "אחרי",
|
||||
"date_before": "לפני",
|
||||
"size_min": "גדול מ־(KB)",
|
||||
"size_max": "קטן מ־(KB)",
|
||||
"starred": "מסומן בכוכב",
|
||||
"unread": "לא נקרא",
|
||||
"read": "קרא",
|
||||
@@ -3133,6 +3204,10 @@
|
||||
"hint": "לחץ על אימייל כלשהו מימין כדי להתחיל, או צא לסיור למטה."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "עריכת מסמך",
|
||||
"office_loading": "פותח את עורך המסמכים…",
|
||||
"office_error": "לא ניתן היה לפתוח את עורך המסמכים.",
|
||||
"office_close": "סגירת העורך",
|
||||
"title": "קבצים",
|
||||
"search_placeholder": "חפש קבצים...",
|
||||
"empty_state_title": "אין עדיין קבצים",
|
||||
@@ -3144,6 +3219,8 @@
|
||||
"rename": "שנה שם",
|
||||
"rename_title": "שנה שם",
|
||||
"new_name": "שם חדש",
|
||||
"name_forbidden_chars": "השרת אינו מתיר את התווים האלה בשמות: {chars}",
|
||||
"name_reserved": "השם הזה שמור בשרת.",
|
||||
"delete": "לִמְחוֹק",
|
||||
"delete_confirm_title": "מחק משאב",
|
||||
"delete_confirm_message": "האם אתה בטוח שברצונך למחוק את \"{name}\"? לא ניתן לבטל זאת.",
|
||||
@@ -3418,6 +3495,7 @@
|
||||
"new_subfolder": "תת־תיקייה חדשה…",
|
||||
"new_folder": "תיקייה חדשה…",
|
||||
"rename": "שנה שם…",
|
||||
"share": "שיתוף...",
|
||||
"import_email": "ייבא .eml או .zip…",
|
||||
"empty_folder": "תיקייה ריקה",
|
||||
"empty_folder_generic": "תיקייה ריקה",
|
||||
@@ -3447,7 +3525,29 @@
|
||||
"toast_error_rename": "נכשל בשינוי שם תיקייה",
|
||||
"toast_error_delete": "נכשל במחיקת תיקייה",
|
||||
"toast_error_delete_has_children": "לתיקייה יש תת־תיקיות. הסר אותן קודם.",
|
||||
"toast_error_delete_has_email": "התיקייה אינה ריקה. תרוקנה קודם."
|
||||
"toast_error_delete_has_email": "התיקייה אינה ריקה. תרוקנה קודם.",
|
||||
"delete_with_contents_title": "התיקייה אינה ריקה",
|
||||
"delete_with_contents_message": "התיקייה \"{name}\" עדיין מכילה {count, plural, one {הודעה אחת} other {# הודעות}}. למחוק את התיקייה ואת כל ההודעות שבה? לא ניתן לבטל פעולה זו.",
|
||||
"delete_with_contents_confirm": "מחיקת התיקייה וההודעות"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "מישהו",
|
||||
"invited": "{name} הזמין/ה אותך ל\"{title}\"",
|
||||
"updated": "{name} עדכן/ה את \"{title}\"",
|
||||
"cancelled": "{name} ביטל/ה את \"{title}\"",
|
||||
"untitled": "אירוע ללא כותרת"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "מישהו",
|
||||
"shared": "{name} שיתף/ה איתך את {kind} \"{object}\"",
|
||||
"changed": "{name} שינה/תה את הגישה שלך ל{kind} \"{object}\"",
|
||||
"revoked": "{name} הסיר/ה את הגישה שלך ל{kind} \"{object}\"",
|
||||
"object": {
|
||||
"folder": "התיקייה",
|
||||
"calendar": "היומן",
|
||||
"address_book": "פנקס הכתובות",
|
||||
"files": "תיקיית הקבצים"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "שתף \"{name}\"",
|
||||
@@ -3502,5 +3602,45 @@
|
||||
"copy_event": "העתק קישור לאירוע",
|
||||
"copied": "הקישור הועתק",
|
||||
"copy_failed": "לא ניתן להעתיק את הקישור"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "משלים אימות…",
|
||||
"completing_message": "אנא המתן בזמן יצירת החיבור.",
|
||||
"received_title": "האימות התקבל",
|
||||
"received_message": "באפשרותך לחזור לכרטיסייה שבה התחלת את החיבור.",
|
||||
"failed_title": "האימות נכשל",
|
||||
"denied": "האימות נדחה.",
|
||||
"failed_detail": "האימות נכשל: {error}",
|
||||
"missing_params": "חסר קוד אימות או מצב.",
|
||||
"delivery_failed": "לא ניתן היה למסור את תוצאת האימות לכרטיסיית האפליקציה. סגרו חלון זה ונסו שוב."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "חיפוש בכל מקום",
|
||||
"placeholder": "חיפוש בדואר, אנשי קשר, יומן, קבצים...",
|
||||
"scope_label": "חיפוש ב",
|
||||
"account_label": "חשבון",
|
||||
"scope_all": "הכל",
|
||||
"scope_mail": "דוֹאַר",
|
||||
"scope_contacts": "אנשי קשר",
|
||||
"scope_calendar": "לוּחַ שָׁנָה",
|
||||
"scope_files": "קבצים",
|
||||
"all_accounts": "כל החשבונות",
|
||||
"show_all": "הצגת כל התוצאות",
|
||||
"no_results": "אין תוצאות עבור “{query}”",
|
||||
"server_hint": "מילים שלמות מחפשות בשרת; חלקי מילים תואמים פריטים שכבר נטענו",
|
||||
"searching": "מחפש...",
|
||||
"account_error": "לא ניתן היה לחפש ב-{account}",
|
||||
"recent_searches": "חיפושים אחרונים",
|
||||
"recurring": "חוזר",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "חפשו דואר, אנשי קשר, אירועים וקבצים בכל החשבונות. נסו: from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "שטוח לפי תאריך",
|
||||
"grouped": "מקובץ",
|
||||
"load_more": "טען עוד",
|
||||
"preview_empty": "בחרו תוצאה לתצוגה מקדימה",
|
||||
"open_full": "פתיחה",
|
||||
"view_label": "תצוגה"
|
||||
}
|
||||
}
|
||||
|
||||
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "és {count} további",
|
||||
"draft": "Piszkozat",
|
||||
"starred": "Csillagozott",
|
||||
"pinned": "Kitűzve",
|
||||
"replied": "Megválaszolva",
|
||||
"forwarded": "Továbbítva",
|
||||
"has_attachment": "Melléklettel",
|
||||
"conversations_count": "{count} / {total} beszélgetés",
|
||||
"conversations_count_plus": "{count}+ beszélgetés",
|
||||
"conversations_count_simple": "{count} beszélgetés",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Továbbítás",
|
||||
"delete": "Törlés",
|
||||
"archive": "Archiválás",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Csillagozás",
|
||||
"unstar": "Csillagozás megszüntetése",
|
||||
"mark_unread": "Megjelölés olvasatlanként",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Áthelyezés",
|
||||
"print": "Nyomtatás",
|
||||
"view_source": "Forrás megtekintése",
|
||||
|
||||
"fullscreen": "Teljes képernyő",
|
||||
|
||||
"exit_fullscreen": "Kilépés a teljes képernyőből",
|
||||
"export_email": "Exportálás .eml-ként",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Továbbítás mellékletként",
|
||||
"import_email": "Importálás .eml vagy .zip fájlból",
|
||||
"keyboard_shortcuts": "Billentyűparancsok (?)",
|
||||
"email_source": "E-mail forrás",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Olvasási visszaigazolás kérve (kattints a letiltáshoz)",
|
||||
"read_receipt_off": "Olvasási visszaigazolás kérése",
|
||||
"dsn_on": "Kézbesítési értesítés kérve (kattints a kikapcsoláshoz)",
|
||||
"dsn_off": "Kézbesítési értesítés kérése",
|
||||
"format_plain_text": "Váltás egyszerű szövegre",
|
||||
"format_rich_text": "Váltás formázott szövegre (HTML)",
|
||||
"require_tls_on": "Titkosított kézbesítés kötelező (kattints a kikapcsoláshoz)",
|
||||
"require_tls_off": "Titkosított kézbesítés megkövetelése (TLS)",
|
||||
"new_message": "Új üzenet",
|
||||
"signature_edit_hint": "Kattintson duplán az aláírás szerkesztéséhez",
|
||||
"reply": "Válasz",
|
||||
@@ -634,11 +643,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Felülbírálás",
|
||||
"toggle_on": "Felülbírálás megszüntetése",
|
||||
"toggle_tooltip": "A Feladó név és cím szabad szerkesztése. A levél továbbra is az identitásodon keresztül kerül elküldésre - csak a látható Feladó fejléc változik.",
|
||||
"toggle_tooltip": "A Feladó név és cím szabad szerkesztése. A levél továbbra is az identitásodon keresztül kerül elküldésre.",
|
||||
"name_label": "Feladó neve",
|
||||
"name_placeholder": "Név",
|
||||
"email_label": "Feladó e-mail címe",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "Ha a szervered nem fogadja el ezt a címet borítékfeladóként, helyette a(z) {identity} cím kerül oda, és a címzettek láthatják a Return-Path fejlécben."
|
||||
},
|
||||
"use_template": "Sablon",
|
||||
"save_as_template": "Mentés sablonként",
|
||||
@@ -651,6 +661,8 @@
|
||||
"upload_progress": "Feltöltés {uploaded} / {total}",
|
||||
"upload_cancel": "Feltöltés megszakítása",
|
||||
"upload_failed": "Nem sikerült feltölteni: {filename}",
|
||||
"attachment_too_large": "A(z) „{name}” nagyobb, mint amit a kiszolgáló enged (fájlonként {max})",
|
||||
"attachments_total_too_large": "A mellékletek túllépnék a kiszolgáló üzenetenkénti {max} korlátját",
|
||||
"drop_files": "Fájlok ejtése a csatoláshoz",
|
||||
"show_less": "Kevesebb mutatása",
|
||||
"send_failed": "Nem sikerült elküldeni az e-mailt",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Oszlop törlése",
|
||||
"toggle_header_row": "Fejlécsor váltása",
|
||||
"delete_table": "Táblázat törlése",
|
||||
"pick_size": "Méret kiválasztása"
|
||||
"pick_size": "Méret kiválasztása",
|
||||
"font_size": "Betűméret",
|
||||
"font_size_default": "Alapértelmezett",
|
||||
"background_color": "Háttérszín",
|
||||
"remove_background_color": "Háttérszín eltávolítása"
|
||||
},
|
||||
"send_filing_warning": "Elküldve - de az utólagos rendrakás nem sikerült, egy elavult piszkozat megmaradhat."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "E-mail sikeresen elküldve",
|
||||
"email_deleted": "E-mail törölve",
|
||||
"email_archived": "E-mail archiválva",
|
||||
"emails_deleted": "{count, plural, other {# e-mail törölve}}",
|
||||
"emails_archived": "{count, plural, other {# e-mail archiválva}}",
|
||||
"error_archiving": "Nem sikerült archiválni az e-mailt",
|
||||
"error_updating": "Nem sikerült frissíteni az e-mailt",
|
||||
"email_starred": "E-mail csillagozva",
|
||||
"email_unstarred": "E-mail csillagozás megszüntetve",
|
||||
"email_marked_read": "E-mail olvasottnak jelölve",
|
||||
@@ -1144,6 +1164,7 @@
|
||||
"status_active": "Aktív ezen az eszközön",
|
||||
"status_inactive": "Nincs engedélyezve ezen az eszközön",
|
||||
"status_unsupported": "Ez a böngésző nem támogatja a Web Push-t",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"status_busy": "Munka...",
|
||||
"enable": "Engedélyezés",
|
||||
"reenable": "Újraregisztráció",
|
||||
@@ -1181,7 +1202,9 @@
|
||||
"enabled": "E-mail értesítések",
|
||||
"enabled_desc": "Értesítések megjelenítése új e-mailek érkezésekor",
|
||||
"sound": "Értesítési hang",
|
||||
"sound_desc": "Hangjelzés lejátszása új e-mailek érkezésekor"
|
||||
"sound_desc": "Hangjelzés lejátszása új e-mailek érkezésekor",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Naptár értesítések",
|
||||
@@ -1241,6 +1264,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Keresés törlése mappaváltáskor",
|
||||
"description": "Egy mappára kattintva (Beérkezett, Elküldött…) törlődik az aktív keresés, és a mappa jelenik meg. Kikapcsolva a keresés megmarad, és újra lefut a választott mappában."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Olvasási visszaigazolás kérése alapértelmezés szerint",
|
||||
"description": "Az olvasási visszaigazolás kérésének előre bekapcsolása új üzenet írásakor."
|
||||
@@ -1324,6 +1351,12 @@
|
||||
"label": "Válasz a fogadott címről",
|
||||
"description": "Válaszküldéskor arról a címről küldjön, amelyre az üzenet eredetileg érkezett. Először identitásokat keres; tartományi catch-all kézbesítések esetén átírja a Feladó fejlécet az aliasra, miközben az elsődleges identitáson keresztül küld."
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Fogadó cím egyeztetése",
|
||||
"description": "Mely fogadó címek számítanak a sajátjának. A „Csak pontos cím” kizárólag a beállított identitásai közül választ; az „azonos domain” az identitás-domainjein lévő bármely más címet is catch-all aliasként kezeli, és arra írja át a Feladó fejlécet. Válassza a pontos címet, ha ezek a címek terjesztési listák.",
|
||||
"exact": "Csak pontos cím",
|
||||
"domain": "Bármely cím a domainjeimen"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Aláírás pozíciója",
|
||||
"description": "Hova kerüljön az aláírás a válaszokban és továbbításokban. Az idézett szöveg felett természetes lezárásként olvasható; alatta az eredeti üzenet marad egybefüggő.",
|
||||
@@ -1749,7 +1782,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "A befejező dátumnak a kezdő dátum után kell lennie",
|
||||
"start_in_past": "A kezdő dátum a múltban van",
|
||||
"empty_body": "Az üzenet szövege üres - a címzettek üres választ kapnak"
|
||||
"empty_body": "Az üzenet szövege üres - a címzettek üres választ kapnak",
|
||||
"subject_too_long": "A tárgy túl hosszú a kiszolgálónak (legfeljebb {max} bájt).",
|
||||
"body_too_long": "Az üzenet túl hosszú a kiszolgálónak (legfeljebb {max} bájt a formázással együtt)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1766,6 +1801,8 @@
|
||||
"role_trash": "Kuka",
|
||||
"role_junk": "Spam",
|
||||
"role_archive": "Archívum",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Nincs",
|
||||
"create_folder": "Mappa létrehozása",
|
||||
"create_subfolder": "Almappa létrehozása",
|
||||
@@ -1875,6 +1912,8 @@
|
||||
"title": "Névjegyek",
|
||||
"description": "Névjegyek importálása és exportálása",
|
||||
"group_by_letter_label": "Csoportosítás kezdőbetű szerint",
|
||||
"sort_by_last_name_label": "Rendezés vezetéknév szerint",
|
||||
"sort_by_last_name_description": "A névjegylista vezetéknév szerinti rendezése, hogy a családtagok egymás mellett legyenek",
|
||||
"group_by_letter_description": "ABC sorrendű fejléc mutatása a névjegy listában",
|
||||
"import_label": "Névjegyek importálása",
|
||||
"import_description": "Névjegyek importálása vCard (.vcf) fájlból",
|
||||
@@ -1966,6 +2005,7 @@
|
||||
"actions": "Műveletek",
|
||||
"add_action": "Művelet hozzáadása",
|
||||
"stop_processing": "További szabályok feldolgozásának leállítása",
|
||||
"include_spam": "A levélszemétnek jelölt üzenetek áthelyezése is",
|
||||
"attachment_type_placeholder": "pl. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Érték (több érték vesszővel elválasztva)",
|
||||
"condition_fields": {
|
||||
@@ -2007,6 +2047,8 @@
|
||||
"copy_to_folder": "Mappa kiválasztása",
|
||||
"forward_to": "Továbbítás e-mail címre",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "Másolat megtartása",
|
||||
"forward_limit": "Továbbítási korlát üzenetenként ezen a kiszolgálón: {count}. A további továbbítások kimaradnak.",
|
||||
"reject_message": "Elutasító üzenet",
|
||||
"reject_placeholder": "Az e-mailed elutasításra került",
|
||||
"label_name": "Címke neve",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Új almappa...",
|
||||
"new_folder": "Új mappa...",
|
||||
"rename": "Átnevezés...",
|
||||
"share": "Megosztás...",
|
||||
"import_email": "Importálás .eml vagy .zip fájlból...",
|
||||
"empty_folder": "Mappa ürítése",
|
||||
"empty_folder_generic": "Mappa ürítése",
|
||||
@@ -2236,7 +2279,10 @@
|
||||
"toast_error_rename": "Nem sikerült átnevezni a mappát",
|
||||
"toast_error_delete": "Nem sikerült törölni a mappát",
|
||||
"toast_error_delete_has_children": "A mappának almappái vannak. Távolítsd el azokat először.",
|
||||
"toast_error_delete_has_email": "A mappa nem üres. Ürítsd ki először."
|
||||
"toast_error_delete_has_email": "A mappa nem üres. Ürítsd ki először.",
|
||||
"delete_with_contents_title": "A mappa nem üres",
|
||||
"delete_with_contents_message": "A(z) „{name}” mappa még {count, plural, one {1 üzenetet} other {# üzenetet}} tartalmaz. Törlöd a mappát az összes üzenetével együtt? Ez nem vonható vissza.",
|
||||
"delete_with_contents_confirm": "Mappa és üzenetek törlése"
|
||||
},
|
||||
"shortcuts": {
|
||||
"title": "Billentyűparancsok",
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Keresés fókusz",
|
||||
"help": "Parancsok mutatása",
|
||||
"refresh": "E-mailek frissítése",
|
||||
"select_all": "Összes kijelölése"
|
||||
"select_all": "Összes kijelölése",
|
||||
"global_search": "Keresés mindenben"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Beszélgetés kibontása/összecsukása"
|
||||
@@ -2722,7 +2769,10 @@
|
||||
"any_month": "Bármely hónap",
|
||||
"has_email": "Van e-mail",
|
||||
"has_phone": "Van telefon",
|
||||
"has_photo": "Van fotó"
|
||||
"has_photo": "Van fotó",
|
||||
"sort_by": "Rendezés",
|
||||
"sort_first_name": "Keresztnév",
|
||||
"sort_last_name": "Vezetéknév"
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "Duplikálás",
|
||||
"today_header": "Ma",
|
||||
"tomorrow_header": "Holnap",
|
||||
"agenda_show_earlier": "Korábbi események megjelenítése",
|
||||
"agenda_loading": "Betöltés…",
|
||||
"agenda_range_start": "Események ettől: {date}",
|
||||
"agenda_range_end": "Események eddig: {date}",
|
||||
"export_ics": "Exportálás .ics-ként",
|
||||
"copy_title": "Cím másolása",
|
||||
"copy_link": "Találkozó link másolása",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "Szín"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Elérhetőség",
|
||||
"free": "Szabad",
|
||||
"busy": "Foglalt",
|
||||
"tentative": "Feltételes",
|
||||
"unknown": "Nem felhasználó ezen a kiszolgálón",
|
||||
"checking": "Ellenőrzés…"
|
||||
},
|
||||
"title": "Résztvevők",
|
||||
"add": "Résztvevő hozzáadása",
|
||||
"organizer": "Szervező",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Minden esemény",
|
||||
"cancel": "Mégse",
|
||||
"save": "Mentés",
|
||||
"delete": "Törlés"
|
||||
"delete": "Törlés",
|
||||
"rsvp_title": "Válasz ismétlődő eseményre",
|
||||
"rsvp_description": "Ez egy ismétlődő esemény. Mely eseményekre vonatkozzon a válaszod?",
|
||||
"respond": "Válasz"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Emlékeztetők",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Események időpontjának mutatása a havi naptár nézetben",
|
||||
"show_week_numbers": "Hétszámok mutatása",
|
||||
"show_week_numbers_desc": "Hétszámok mutatása a mini naptárban",
|
||||
"calendar_free_scroll": "Szabad görgetés",
|
||||
"calendar_free_scroll_desc": "Folyamatos görgetés hónapok, hetek és napok között egyetlen időszak helyett",
|
||||
"enable_tasks": "Feladatok engedélyezése",
|
||||
"enable_tasks_desc": "Feladatnézet mutatása a naptárban a teendők kezeléséhez",
|
||||
"show_tasks_on_calendar": "Feladatok mutatása a naptárban",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "1 másodperc késleltetés",
|
||||
"hover_preview_delay_2s": "2 másodperc késleltetés",
|
||||
"hover_preview_off": "Letiltva",
|
||||
"organizer_identity": "Meghívók szervezése mint",
|
||||
"organizer_identity_desc": "A szervezőként használt naptárcím, amikor embereket hívsz meg eseményekre.",
|
||||
"organizer_identity_failed": "Nem sikerült módosítani a szervező címét",
|
||||
"show_birthday_calendar": "Névjegy születésnap naptár",
|
||||
"show_birthday_calendar_desc": "Virtuális naptár mutatása a névjegyeid születésnapjaival"
|
||||
},
|
||||
@@ -2973,6 +3043,9 @@
|
||||
"alert_now": "Most kezdődik",
|
||||
"alert_in_minutes": "{count} perc múlva",
|
||||
"invitation_sent": "Meghívók elküldve",
|
||||
"invitations_denied_title": "A meghívók nem küldhetők el",
|
||||
"invitations_denied": "A kiszolgáló nem küldte el a meghívókat: {reason}. Mentsük az eseményt a meghívók nélkül?",
|
||||
"save_without_invitations": "Mentés meghívók nélkül",
|
||||
"rsvp_updated": "Válasz frissítve",
|
||||
"rsvp_error": "Nem sikerült frissíteni a választ",
|
||||
"event_duplicated": "Esemény duplikálva",
|
||||
@@ -3002,7 +3075,7 @@
|
||||
"supported_formats": "iCalendar (.ics) fájlok támogatottak",
|
||||
"url_description": "Add meg egy külső iCalendar (.ics) feed URL-jét az események importálásához.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "CalDAV és iCalendar (.ics) URL-ek támogatottak",
|
||||
"url_hint": "iCalendar (.ics) URL-ek támogatottak",
|
||||
"fetch": "Lekérés",
|
||||
"invalid_url": "Kérjük, adj meg egy érvényes URL-t",
|
||||
"url_fetch_failed": "Nem sikerült lekérni a naptárt az URL-ről",
|
||||
@@ -3149,6 +3222,25 @@
|
||||
"overdue": "Lejárt"
|
||||
}
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Valaki",
|
||||
"invited": "{name} meghívott a(z) „{title}” eseményre",
|
||||
"updated": "{name} frissítette a(z) „{title}” eseményt",
|
||||
"cancelled": "{name} lemondta a(z) „{title}” eseményt",
|
||||
"untitled": "Névtelen esemény"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Valaki",
|
||||
"shared": "{name} megosztotta veled a(z) {kind} „{object}” elemet",
|
||||
"changed": "{name} módosította a hozzáférésedet a(z) {kind} „{object}” elemhez",
|
||||
"revoked": "{name} visszavonta a hozzáférésedet a(z) {kind} „{object}” elemhez",
|
||||
"object": {
|
||||
"folder": "mappa",
|
||||
"calendar": "naptár",
|
||||
"address_book": "címjegyzék",
|
||||
"files": "fájlmappa"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "\"{name}\" megosztása",
|
||||
"description": "Hozzáférés biztosítása más felhasználók vagy csoportok számára ezen a szerveren. A módosítások azonnal életbe lépnek.",
|
||||
@@ -3187,6 +3279,8 @@
|
||||
"has_attachment": "Csatolmányok",
|
||||
"date_after": "Ettől",
|
||||
"date_before": "Eddig",
|
||||
"size_min": "Nagyobb mint (KB)",
|
||||
"size_max": "Kisebb mint (KB)",
|
||||
"starred": "Csillagozott",
|
||||
"unread": "Olvasatlan",
|
||||
"read": "Olvasott",
|
||||
@@ -3228,6 +3322,10 @@
|
||||
"hint": "Kattints bármely e-mailre a bal oldalon a kezdéshez, vagy nézd meg az alábbi bemutatót."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Dokumentum szerkesztése",
|
||||
"office_loading": "Dokumentumszerkesztő megnyitása…",
|
||||
"office_error": "A dokumentumszerkesztőt nem sikerült megnyitni.",
|
||||
"office_close": "Szerkesztő bezárása",
|
||||
"open_in_new_tab": "Megnyitás új lapon",
|
||||
"title": "Fájlok",
|
||||
"search_placeholder": "Fájlok keresése...",
|
||||
@@ -3240,6 +3338,8 @@
|
||||
"rename": "Átnevezés",
|
||||
"rename_title": "Átnevezés",
|
||||
"new_name": "Új név",
|
||||
"name_forbidden_chars": "A kiszolgáló nem engedi ezeket a karaktereket a nevekben: {chars}",
|
||||
"name_reserved": "Ez a név a kiszolgálón foglalt.",
|
||||
"delete": "Törlés",
|
||||
"delete_confirm_title": "Erőforrás törlése",
|
||||
"delete_confirm_message": "Biztosan törölni szeretnéd a \"{name}\" elemet? Ez nem vonható vissza.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Esemény linkjének másolása",
|
||||
"copied": "Link másolva",
|
||||
"copy_failed": "Nem sikerült másolni a linket"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Hitelesítés befejezése…",
|
||||
"completing_message": "Kérjük, várjon, amíg a kapcsolat létrejön.",
|
||||
"received_title": "Hitelesítés fogadva",
|
||||
"received_message": "Visszatérhet arra a lapra, ahol elindította a kapcsolódást.",
|
||||
"failed_title": "A hitelesítés sikertelen",
|
||||
"denied": "A hitelesítés megtagadva.",
|
||||
"failed_detail": "A hitelesítés sikertelen: {error}",
|
||||
"missing_params": "Hiányzik a hitelesítési kód vagy az állapot.",
|
||||
"delivery_failed": "A hitelesítés eredményét nem sikerült átadni az alkalmazás lapjának. Zárja be ezt az ablakot, és próbálja újra."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Keresés mindenben",
|
||||
"placeholder": "Keresés a levélben, névjegyekben, naptárban, fájlokban...",
|
||||
"scope_label": "Keresés itt",
|
||||
"account_label": "Fiók",
|
||||
"scope_all": "Minden",
|
||||
"scope_mail": "Levelek",
|
||||
"scope_contacts": "Névjegyek",
|
||||
"scope_calendar": "Naptár",
|
||||
"scope_files": "Fájlok",
|
||||
"all_accounts": "Minden fiók",
|
||||
"show_all": "Összes találat megjelenítése",
|
||||
"no_results": "Nincs találat erre: „{query}”",
|
||||
"server_hint": "A teljes szavak a szerveren keresnek; a szórészletek a már betöltött elemekre illeszkednek",
|
||||
"searching": "Keresés...",
|
||||
"account_error": "Nem sikerült keresni itt: {account}",
|
||||
"recent_searches": "Legutóbbi keresések",
|
||||
"recurring": "Ismétlődő",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Keressen levelet, névjegyet, eseményt és fájlt minden fiókban. Próbálja ki: from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Lapos, dátum szerint",
|
||||
"grouped": "Csoportosított",
|
||||
"load_more": "Továbbiak betöltése",
|
||||
"preview_empty": "Válasszon egy találatot az előnézethez",
|
||||
"open_full": "Megnyitás",
|
||||
"view_label": "Nézet"
|
||||
}
|
||||
}
|
||||
|
||||
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "e altri {count}",
|
||||
"draft": "Bozza",
|
||||
"starred": "Speciale",
|
||||
"pinned": "Fissato",
|
||||
"replied": "Risposto",
|
||||
"forwarded": "Inoltrato",
|
||||
"has_attachment": "Con allegato",
|
||||
"conversations_count": "{count} di {total} conversazioni",
|
||||
"conversations_count_plus": "{count}+ conversazioni",
|
||||
"conversations_count_simple": "{count} conversazioni",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Inoltra",
|
||||
"delete": "Elimina",
|
||||
"archive": "Archivia",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Aggiungi stella",
|
||||
"unstar": "Rimuovi stella",
|
||||
"mark_unread": "Segna come non letto",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Sposta",
|
||||
"print": "Stampa",
|
||||
"view_source": "Visualizza sorgente",
|
||||
|
||||
"fullscreen": "Schermo intero",
|
||||
|
||||
"exit_fullscreen": "Esci da schermo intero",
|
||||
"export_email": "Esporta come .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Inoltra come allegato",
|
||||
"import_email": "Importa .eml o .zip",
|
||||
"keyboard_shortcuts": "Scorciatoie da tastiera (?)",
|
||||
"email_source": "Sorgente del messaggio",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Conferma di lettura richiesta (clicca per disattivare)",
|
||||
"read_receipt_off": "Richiedi una conferma di lettura",
|
||||
"dsn_on": "Notifica di consegna richiesta (clic per disattivare)",
|
||||
"dsn_off": "Richiedi una notifica di consegna",
|
||||
"format_plain_text": "Passa a testo semplice",
|
||||
"format_rich_text": "Passa a testo formattato (HTML)",
|
||||
"require_tls_on": "Consegna cifrata obbligatoria (clic per disattivare)",
|
||||
"require_tls_off": "Richiedi consegna cifrata (TLS)",
|
||||
"new_message": "Nuovo messaggio",
|
||||
"signature_edit_hint": "Doppio clic per modificare la firma",
|
||||
"reply": "Rispondi",
|
||||
@@ -631,11 +640,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Sovrascrivi",
|
||||
"toggle_on": "Annulla sovrascrittura",
|
||||
"toggle_tooltip": "Modifica liberamente nome e indirizzo del mittente. La posta viene comunque inviata tramite la tua identità - cambia solo l'intestazione Da visibile.",
|
||||
"toggle_tooltip": "Modifica liberamente nome e indirizzo del mittente. La posta viene comunque inviata tramite la tua identità.",
|
||||
"name_label": "Nome mittente",
|
||||
"name_placeholder": "Nome",
|
||||
"email_label": "Indirizzo email del mittente",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "Se il tuo server non accetta questo indirizzo come mittente della busta, viene usato {identity} al suo posto, e i destinatari possono vederlo nell'intestazione Return-Path."
|
||||
},
|
||||
"use_template": "Modello",
|
||||
"save_as_template": "Salva come modello",
|
||||
@@ -648,6 +658,8 @@
|
||||
"upload_progress": "Caricamento {uploaded} / {total}",
|
||||
"upload_cancel": "Annulla caricamento",
|
||||
"upload_failed": "Caricamento di {filename} non riuscito",
|
||||
"attachment_too_large": "\"{name}\" supera la dimensione consentita dal server ({max} per file)",
|
||||
"attachments_total_too_large": "Gli allegati supererebbero il limite del server di {max} per messaggio",
|
||||
"send_failed": "Invio dell'e-mail non riuscito",
|
||||
"smime_sign_on": "Firma S/MIME attivata",
|
||||
"smime_sign_off": "Firma S/MIME disattivata",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Elimina colonna",
|
||||
"toggle_header_row": "Attiva/disattiva riga di intestazione",
|
||||
"delete_table": "Elimina tabella",
|
||||
"pick_size": "Scegli dimensione"
|
||||
"pick_size": "Scegli dimensione",
|
||||
"font_size": "Dimensione carattere",
|
||||
"font_size_default": "Predefinita",
|
||||
"background_color": "Colore di sfondo",
|
||||
"remove_background_color": "Rimuovi colore di sfondo"
|
||||
},
|
||||
"send_filing_warning": "Inviato - ma la pulizia successiva non è riuscita, potrebbe restare una bozza obsoleta."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "Messaggio inviato con successo",
|
||||
"email_deleted": "Messaggio eliminato",
|
||||
"email_archived": "Messaggio archiviato",
|
||||
"emails_deleted": "{count, plural, one {# email eliminata} other {# email eliminate}}",
|
||||
"emails_archived": "{count, plural, one {# email archiviata} other {# email archiviate}}",
|
||||
"error_archiving": "Impossibile archiviare l'email",
|
||||
"error_updating": "Impossibile aggiornare l'email",
|
||||
"email_starred": "Stella aggiunta al messaggio",
|
||||
"email_unstarred": "Stella rimossa dal messaggio",
|
||||
"email_marked_read": "Messaggio segnato come letto",
|
||||
@@ -1143,7 +1163,9 @@
|
||||
"enabled": "Notifiche e-mail",
|
||||
"enabled_desc": "Mostra notifiche all'arrivo di nuove e-mail",
|
||||
"sound": "Suono di notifica",
|
||||
"sound_desc": "Riproduci un suono all'arrivo di nuove e-mail"
|
||||
"sound_desc": "Riproduci un suono all'arrivo di nuove e-mail",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Notifiche calendario",
|
||||
@@ -1172,6 +1194,7 @@
|
||||
"status_busy": "In elaborazione…",
|
||||
"status_inactive": "Non abilitato su questo dispositivo",
|
||||
"status_unsupported": "Questo browser non supporta Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"title": "Notifiche in background",
|
||||
"devices_title": "Dispositivi registrati",
|
||||
"devices_desc": "Tutti i dispositivi registrati per ricevere le notifiche in background di questo account. Revocandone uno, smette subito di riceverle.",
|
||||
@@ -1238,6 +1261,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Cancella la ricerca quando cambi cartella",
|
||||
"description": "Facendo clic su una cartella (Posta in arrivo, Inviati…) la ricerca attiva viene cancellata e si apre quella cartella. Se disattivato, la ricerca resta attiva e viene rieseguita nella cartella scelta."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Richiedi conferme di lettura per impostazione predefinita",
|
||||
"description": "Attiva in anticipo la richiesta di conferma di lettura durante la composizione di un nuovo messaggio."
|
||||
@@ -1316,6 +1343,12 @@
|
||||
"label": "Seleziona automaticamente l'indirizzo di risposta",
|
||||
"description": "Quando rispondi, passa automaticamente l'indirizzo mittente all'identità che ha ricevuto il messaggio originale"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Corrispondenza dell'indirizzo di ricezione",
|
||||
"description": "Quali indirizzi di ricezione contano come tuoi. «Solo indirizzo esatto» sceglie soltanto una delle tue identità configurate; «stesso dominio» tratta anche qualsiasi altro indirizzo dei tuoi domini di identità come alias catch-all e riscrive con esso l'intestazione Da. Scegli l'indirizzo esatto se quegli indirizzi sono liste di distribuzione.",
|
||||
"exact": "Solo indirizzo esatto",
|
||||
"domain": "Qualsiasi indirizzo dei miei domini"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Posizione della firma",
|
||||
"description": "Dove inserire la tua firma nelle risposte e negli inoltri. Sopra il testo citato si legge in modo naturale come chiusura della risposta; sotto mantiene il messaggio originale contiguo.",
|
||||
@@ -1746,7 +1779,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "La data di fine deve essere successiva alla data di inizio",
|
||||
"start_in_past": "La data di inizio è nel passato",
|
||||
"empty_body": "Il corpo del messaggio è vuoto - i destinatari riceveranno una risposta vuota"
|
||||
"empty_body": "Il corpo del messaggio è vuoto - i destinatari riceveranno una risposta vuota",
|
||||
"subject_too_long": "L'oggetto è troppo lungo per il server (al massimo {max} byte).",
|
||||
"body_too_long": "Il messaggio è troppo lungo per il server (al massimo {max} byte, formattazione inclusa)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1763,6 +1798,8 @@
|
||||
"role_trash": "Cestino",
|
||||
"role_junk": "Spam / Indesiderata",
|
||||
"role_archive": "Archivio",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Nessuno",
|
||||
"create_folder": "Crea cartella",
|
||||
"create_subfolder": "Crea sottocartella",
|
||||
@@ -1882,7 +1919,9 @@
|
||||
"categories_description": "Rinomina le categorie dei contatti",
|
||||
"no_categories": "Nessuna categoria trovata",
|
||||
"group_by_letter_description": "Mostra intestazioni alfabetiche nell'elenco dei contatti",
|
||||
"group_by_letter_label": "Raggruppa per prima lettera"
|
||||
"group_by_letter_label": "Raggruppa per prima lettera",
|
||||
"sort_by_last_name_label": "Ordina per cognome",
|
||||
"sort_by_last_name_description": "Ordina l'elenco dei contatti per cognome così i familiari compaiono insieme"
|
||||
},
|
||||
"downloads": {
|
||||
"title": "Download",
|
||||
@@ -1963,6 +2002,7 @@
|
||||
"actions": "Azioni",
|
||||
"add_action": "Aggiungi azione",
|
||||
"stop_processing": "Interrompere l'elaborazione delle regole successive",
|
||||
"include_spam": "Sposta anche i messaggi contrassegnati come spam",
|
||||
"attachment_type_placeholder": "es. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Valore (più valori separati da virgole)",
|
||||
"condition_fields": {
|
||||
@@ -2004,6 +2044,8 @@
|
||||
"copy_to_folder": "Seleziona cartella",
|
||||
"forward_to": "Inoltra a indirizzo email",
|
||||
"forward_placeholder": "email@esempio.com",
|
||||
"forward_keep_copy": "Conserva una copia",
|
||||
"forward_limit": "Limite di inoltri per messaggio su questo server: {count}. Gli inoltri in eccesso vengono ignorati.",
|
||||
"reject_message": "Messaggio di rifiuto",
|
||||
"reject_placeholder": "La tua email è stata rifiutata",
|
||||
"label_name": "Nome dell'etichetta",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Nuova sottocartella...",
|
||||
"new_folder": "Nuova cartella...",
|
||||
"rename": "Rinomina...",
|
||||
"share": "Condividi...",
|
||||
"import_email": "Importa .eml o .zip...",
|
||||
"empty_folder": "Svuota cartella",
|
||||
"empty_folder_generic": "Svuota cartella",
|
||||
@@ -2234,6 +2277,9 @@
|
||||
"toast_error_delete": "Impossibile eliminare la cartella",
|
||||
"toast_error_delete_has_children": "La cartella contiene sottocartelle. Rimuoverle prima.",
|
||||
"toast_error_delete_has_email": "La cartella non è vuota. Svuotarla prima.",
|
||||
"delete_with_contents_title": "La cartella non è vuota",
|
||||
"delete_with_contents_message": "La cartella \"{name}\" contiene ancora {count, plural, one {1 messaggio} other {# messaggi}}. Eliminare la cartella e tutti i suoi messaggi? L'operazione non può essere annullata.",
|
||||
"delete_with_contents_confirm": "Elimina cartella e messaggi",
|
||||
"placeholder_folder_name": "Nome cartella",
|
||||
"create": "Crea",
|
||||
"rename_confirm": "Rinomina"
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Cerca",
|
||||
"help": "Mostra scorciatoie",
|
||||
"refresh": "Aggiorna messaggi",
|
||||
"select_all": "Seleziona tutto"
|
||||
"select_all": "Seleziona tutto",
|
||||
"global_search": "Cerca ovunque"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Espandi/comprimi conversazione"
|
||||
@@ -2721,7 +2768,10 @@
|
||||
"any_month": "Qualsiasi mese",
|
||||
"has_email": "Con email",
|
||||
"has_phone": "Con telefono",
|
||||
"has_photo": "Con foto"
|
||||
"has_photo": "Con foto",
|
||||
"sort_by": "Ordina per",
|
||||
"sort_first_name": "Nome",
|
||||
"sort_last_name": "Cognome"
|
||||
},
|
||||
"open_categories": "Apri categorie"
|
||||
},
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "Duplica",
|
||||
"today_header": "Oggi",
|
||||
"tomorrow_header": "Domani",
|
||||
"agenda_show_earlier": "Mostra eventi precedenti",
|
||||
"agenda_loading": "Caricamento…",
|
||||
"agenda_range_start": "Eventi dal {date}",
|
||||
"agenda_range_end": "Eventi fino al {date}",
|
||||
"export_ics": "Esporta come .ics",
|
||||
"copy_title": "Copia titolo",
|
||||
"copy_link": "Copia link riunione",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "Colore"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Disponibilità",
|
||||
"free": "Disponibile",
|
||||
"busy": "Occupato",
|
||||
"tentative": "Provvisorio",
|
||||
"unknown": "Non è un utente di questo server",
|
||||
"checking": "Verifica in corso…"
|
||||
},
|
||||
"title": "Partecipanti",
|
||||
"add": "Aggiungi partecipante",
|
||||
"organizer": "Organizzatore",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Tutti gli eventi",
|
||||
"cancel": "Annulla",
|
||||
"save": "Salva",
|
||||
"delete": "Elimina"
|
||||
"delete": "Elimina",
|
||||
"rsvp_title": "Rispondi a evento ricorrente",
|
||||
"rsvp_description": "Questo è un evento ricorrente. A quali eventi si applica la tua risposta?",
|
||||
"respond": "Rispondi"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Promemoria",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Visualizza gli orari degli eventi nella vista mensile del calendario",
|
||||
"show_week_numbers": "Mostra numeri di settimana",
|
||||
"show_week_numbers_desc": "Mostra i numeri di settimana nel mini-calendario",
|
||||
"calendar_free_scroll": "Scorrimento libero",
|
||||
"calendar_free_scroll_desc": "Scorri in modo continuo tra mesi, settimane e giorni invece di un periodo alla volta",
|
||||
"enable_tasks": "Abilita attività",
|
||||
"enable_tasks_desc": "Mostra una vista attività nel calendario per gestire le cose da fare",
|
||||
"show_tasks_on_calendar": "Mostra attività nel calendario",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "Ritardo di 1 secondo",
|
||||
"hover_preview_delay_2s": "Ritardo di 2 secondi",
|
||||
"hover_preview_off": "Disabilitato",
|
||||
"organizer_identity": "Organizza gli inviti come",
|
||||
"organizer_identity_desc": "L'indirizzo del calendario usato come organizzatore quando inviti persone agli eventi.",
|
||||
"organizer_identity_failed": "Impossibile cambiare l'indirizzo dell'organizzatore",
|
||||
"show_birthday_calendar": "Calendario compleanni",
|
||||
"show_birthday_calendar_desc": "Mostra un calendario virtuale con i compleanni dei tuoi contatti"
|
||||
},
|
||||
@@ -2947,6 +3017,9 @@
|
||||
"alert_now": "Inizia ora",
|
||||
"alert_in_minutes": "Tra {count} min",
|
||||
"invitation_sent": "Inviti inviati",
|
||||
"invitations_denied_title": "Impossibile inviare gli inviti",
|
||||
"invitations_denied": "Il server ha rifiutato di inviare gli inviti: {reason}. Salvare l'evento senza inviarli?",
|
||||
"save_without_invitations": "Salva senza inviti",
|
||||
"rsvp_updated": "Risposta aggiornata",
|
||||
"rsvp_error": "Impossibile aggiornare la risposta",
|
||||
"event_duplicated": "Evento duplicato",
|
||||
@@ -2975,7 +3048,7 @@
|
||||
"supported_formats": "File iCalendar (.ics) supportati",
|
||||
"url_description": "Inserisci l'URL di un feed iCalendar (.ics) esterno per importare eventi.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "Supporta URL CalDAV e iCalendar (.ics)",
|
||||
"url_hint": "Supporta URL iCalendar (.ics)",
|
||||
"fetch": "Recupera",
|
||||
"invalid_url": "Inserisci un URL valido",
|
||||
"url_fetch_failed": "Impossibile recuperare il calendario dall'URL",
|
||||
@@ -3164,6 +3237,8 @@
|
||||
"has_attachment": "Allegati",
|
||||
"date_after": "Dopo il",
|
||||
"date_before": "Prima del",
|
||||
"size_min": "Maggiore di (KB)",
|
||||
"size_max": "Minore di (KB)",
|
||||
"starred": "Preferiti",
|
||||
"unread": "Non letti",
|
||||
"read": "Letti",
|
||||
@@ -3205,6 +3280,10 @@
|
||||
"hint": "Clicca su un'email a sinistra per iniziare, oppure fai il tour."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Modifica documento",
|
||||
"office_loading": "Apertura dell'editor di documenti…",
|
||||
"office_error": "Impossibile aprire l'editor di documenti.",
|
||||
"office_close": "Chiudi editor",
|
||||
"open_in_new_tab": "Apri in una nuova scheda",
|
||||
"title": "File",
|
||||
"search_placeholder": "Cerca file...",
|
||||
@@ -3217,6 +3296,8 @@
|
||||
"rename": "Rinomina",
|
||||
"rename_title": "Rinomina",
|
||||
"new_name": "Nuovo nome",
|
||||
"name_forbidden_chars": "Il server non consente questi caratteri nei nomi: {chars}",
|
||||
"name_reserved": "Questo nome è riservato sul server.",
|
||||
"delete": "Elimina",
|
||||
"delete_confirm_title": "Elimina risorsa",
|
||||
"delete_confirm_message": "Sei sicuro di voler eliminare \"{name}\"? Questa azione non può essere annullata.",
|
||||
@@ -3451,6 +3532,25 @@
|
||||
"unified_mailbox": {
|
||||
"search_unavailable": "La ricerca non è disponibile nella vista unificata"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Qualcuno",
|
||||
"invited": "{name} ti ha invitato a \"{title}\"",
|
||||
"updated": "{name} ha aggiornato \"{title}\"",
|
||||
"cancelled": "{name} ha annullato \"{title}\"",
|
||||
"untitled": "Evento senza titolo"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Qualcuno",
|
||||
"shared": "{name} ha condiviso con te {kind} \"{object}\"",
|
||||
"changed": "{name} ha modificato il tuo accesso a {kind} \"{object}\"",
|
||||
"revoked": "{name} ha rimosso il tuo accesso a {kind} \"{object}\"",
|
||||
"object": {
|
||||
"folder": "la cartella",
|
||||
"calendar": "il calendario",
|
||||
"address_book": "la rubrica",
|
||||
"files": "la cartella dei file"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Condividi \"{name}\"",
|
||||
"description": "Concedi l'accesso ad altri utenti o gruppi su questo server. Le modifiche hanno effetto immediato.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Copia link all'evento",
|
||||
"copied": "Link copiato",
|
||||
"copy_failed": "Impossibile copiare il link"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Completamento dell'autorizzazione…",
|
||||
"completing_message": "Attendi mentre viene stabilita la connessione.",
|
||||
"received_title": "Autorizzazione ricevuta",
|
||||
"received_message": "Puoi tornare alla scheda in cui hai avviato la connessione.",
|
||||
"failed_title": "Autorizzazione non riuscita",
|
||||
"denied": "Lautorizzazione è stata negata.",
|
||||
"failed_detail": "Autorizzazione non riuscita: {error}",
|
||||
"missing_params": "Codice di autorizzazione o stato mancante.",
|
||||
"delivery_failed": "Impossibile consegnare il risultato dellautorizzazione alla scheda dellapp. Chiudi questa finestra e riprova."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Cerca ovunque",
|
||||
"placeholder": "Cerca email, contatti, calendario, file...",
|
||||
"scope_label": "Cerca in",
|
||||
"account_label": "Account",
|
||||
"scope_all": "Tutto",
|
||||
"scope_mail": "Posta",
|
||||
"scope_contacts": "Contatti",
|
||||
"scope_calendar": "Calendario",
|
||||
"scope_files": "File",
|
||||
"all_accounts": "Tutti gli account",
|
||||
"show_all": "Mostra tutti i risultati",
|
||||
"no_results": "Nessun risultato per “{query}”",
|
||||
"server_hint": "Le parole complete cercano sul server; quelle parziali corrispondono agli elementi già caricati",
|
||||
"searching": "Ricerca in corso...",
|
||||
"account_error": "Impossibile cercare in {account}",
|
||||
"recent_searches": "Ricerche recenti",
|
||||
"recurring": "Ricorrente",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Cerca email, contatti, eventi e file in tutti gli account. Prova from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Piatto per data",
|
||||
"grouped": "Raggruppato",
|
||||
"load_more": "Carica altri",
|
||||
"preview_empty": "Seleziona un risultato per l'anteprima",
|
||||
"open_full": "Apri",
|
||||
"view_label": "Vista"
|
||||
}
|
||||
}
|
||||
|
||||
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "他{count}人",
|
||||
"draft": "下書き",
|
||||
"starred": "スター付き",
|
||||
"pinned": "ピン留め",
|
||||
"replied": "返信済み",
|
||||
"forwarded": "転送済み",
|
||||
"has_attachment": "添付ファイルあり",
|
||||
"conversations_count": "{total}件中{count}件の会話",
|
||||
"conversations_count_plus": "{count}件以上の会話",
|
||||
"conversations_count_simple": "{count}件の会話",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "転送",
|
||||
"delete": "削除",
|
||||
"archive": "アーカイブ",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "スターを付ける",
|
||||
"unstar": "スターを外す",
|
||||
"mark_unread": "未読にする",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "移動",
|
||||
"print": "印刷",
|
||||
"view_source": "ソースを表示",
|
||||
|
||||
"fullscreen": "全画面表示",
|
||||
|
||||
"exit_fullscreen": "全画面表示を終了",
|
||||
"export_email": ".emlとしてエクスポート",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "添付ファイルとして転送",
|
||||
"import_email": ".eml または .zip をインポート",
|
||||
"keyboard_shortcuts": "キーボードショートカット (?)",
|
||||
"email_source": "メールソース",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "開封確認を要求中(クリックで無効化)",
|
||||
"read_receipt_off": "開封確認を要求する",
|
||||
"dsn_on": "配信通知を要求中(クリックで解除)",
|
||||
"dsn_off": "配信通知を要求する",
|
||||
"format_plain_text": "プレーンテキストに切り替え",
|
||||
"format_rich_text": "リッチテキスト (HTML) に切り替え",
|
||||
"require_tls_on": "暗号化配信を必須にしています(クリックで解除)",
|
||||
"require_tls_off": "暗号化配信を必須にする(TLS)",
|
||||
"new_message": "新規メッセージ",
|
||||
"signature_edit_hint": "ダブルクリックで署名を編集",
|
||||
"reply": "返信",
|
||||
@@ -631,11 +640,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "上書き",
|
||||
"toggle_on": "上書きを取り消す",
|
||||
"toggle_tooltip": "差出人名とアドレスを自由に編集できます。メールは引き続きあなたのアイデンティティ経由で送信されます - 表示される差出人ヘッダーのみが変更されます。",
|
||||
"toggle_tooltip": "差出人名とアドレスを自由に編集できます。メールは引き続きあなたのアイデンティティ経由で送信されます。",
|
||||
"name_label": "差出人名",
|
||||
"name_placeholder": "名前",
|
||||
"email_label": "差出人メールアドレス",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "サーバーがこのアドレスをエンベロープ送信者として受け付けない場合は、代わりに {identity} が使われ、受信者は Return-Path ヘッダーでそのアドレスを確認できます。"
|
||||
},
|
||||
"use_template": "テンプレート",
|
||||
"save_as_template": "テンプレートとして保存",
|
||||
@@ -648,6 +658,8 @@
|
||||
"upload_progress": "アップロード中 {uploaded} / {total}",
|
||||
"upload_cancel": "アップロードをキャンセル",
|
||||
"upload_failed": "{filename} のアップロードに失敗しました",
|
||||
"attachment_too_large": "「{name}」はサーバーの上限を超えています(1 ファイルあたり {max})",
|
||||
"attachments_total_too_large": "添付ファイルがサーバーの上限(1 通あたり {max})を超えます",
|
||||
"send_failed": "メールの送信に失敗しました",
|
||||
"smime_sign_on": "S/MIME 署名を有効化しました",
|
||||
"smime_sign_off": "S/MIME 署名を無効化しました",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "列を削除",
|
||||
"toggle_header_row": "ヘッダー行の切り替え",
|
||||
"delete_table": "表を削除",
|
||||
"pick_size": "サイズを選択"
|
||||
"pick_size": "サイズを選択",
|
||||
"font_size": "文字サイズ",
|
||||
"font_size_default": "デフォルト",
|
||||
"background_color": "背景色",
|
||||
"remove_background_color": "背景色を削除"
|
||||
},
|
||||
"send_filing_warning": "送信されましたが、送信後の整理に失敗しました。古い下書きが残る場合があります。"
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "メールを送信しました",
|
||||
"email_deleted": "メールを削除しました",
|
||||
"email_archived": "メールをアーカイブしました",
|
||||
"emails_deleted": "{count, plural, other {#件のメールを削除しました}}",
|
||||
"emails_archived": "{count, plural, other {#件のメールをアーカイブしました}}",
|
||||
"error_archiving": "メールをアーカイブできませんでした",
|
||||
"error_updating": "メールを更新できませんでした",
|
||||
"email_starred": "スターを付けました",
|
||||
"email_unstarred": "スターを外しました",
|
||||
"email_marked_read": "既読にしました",
|
||||
@@ -1143,7 +1163,9 @@
|
||||
"enabled": "メール通知",
|
||||
"enabled_desc": "新しいメールが届いたときに通知を表示",
|
||||
"sound": "通知音",
|
||||
"sound_desc": "新しいメールが届いたときに音を鳴らす"
|
||||
"sound_desc": "新しいメールが届いたときに音を鳴らす",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "カレンダー通知",
|
||||
@@ -1172,6 +1194,7 @@
|
||||
"status_busy": "処理中…",
|
||||
"status_inactive": "このデバイスでは有効になっていません",
|
||||
"status_unsupported": "このブラウザは Web Push をサポートしていません",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"title": "バックグラウンド通知",
|
||||
"devices_title": "登録済みのデバイス",
|
||||
"devices_desc": "このアカウントのバックグラウンド通知を受け取るよう登録されているデバイスの一覧です。取り消すと、そのデバイスはすぐに受信を停止します。",
|
||||
@@ -1238,6 +1261,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "フォルダー切り替え時に検索をクリア",
|
||||
"description": "フォルダー(受信トレイ、送信済みなど)をクリックすると、有効な検索をクリアしてそのフォルダーを表示します。オフの場合、検索は保持され、切り替え先のフォルダーで再実行されます。"
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "既定で開封確認を要求する",
|
||||
"description": "新しいメッセージの作成時に、開封確認の要求をあらかじめ有効にします。"
|
||||
@@ -1316,6 +1343,12 @@
|
||||
"label": "返信元アドレスを自動選択",
|
||||
"description": "返信時に、元のメッセージを受信したIDへ差出人アドレスを自動的に切り替えます"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "受信アドレスの照合",
|
||||
"description": "どの受信アドレスを自分のものとみなすか。「完全一致のみ」は設定済みのアイデンティティからのみ選択します。「同じドメイン」はアイデンティティのドメイン上の他のアドレスもキャッチオールのエイリアスとして扱い、From ヘッダーをそのアドレスに書き換えます。それらのアドレスが配布リストの場合は完全一致を選択してください。",
|
||||
"exact": "完全一致のみ",
|
||||
"domain": "自分のドメイン上の任意のアドレス"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "署名の位置",
|
||||
"description": "返信や転送で署名を挿入する位置。引用テキストの上は返信の締めとして自然に読めます。下は元のメッセージを続けて表示します。",
|
||||
@@ -1746,7 +1779,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "終了日は開始日より後に設定してください",
|
||||
"start_in_past": "開始日が過去の日付です",
|
||||
"empty_body": "メッセージ本文が空です - 受信者には空の返信が届きます"
|
||||
"empty_body": "メッセージ本文が空です - 受信者には空の返信が届きます",
|
||||
"subject_too_long": "件名がサーバーの上限を超えています(最大 {max} バイト)。",
|
||||
"body_too_long": "メッセージがサーバーの上限を超えています(書式を含めて最大 {max} バイト)。"
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1763,6 +1798,8 @@
|
||||
"role_trash": "ゴミ箱",
|
||||
"role_junk": "迷惑メール",
|
||||
"role_archive": "アーカイブ",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "なし",
|
||||
"create_folder": "フォルダーを作成",
|
||||
"create_subfolder": "サブフォルダーを作成",
|
||||
@@ -1882,7 +1919,9 @@
|
||||
"categories_description": "連絡先カテゴリの名前を変更",
|
||||
"no_categories": "カテゴリが見つかりません",
|
||||
"group_by_letter_description": "連絡先リストにアルファベット順のセクション見出しを表示",
|
||||
"group_by_letter_label": "頭文字でグループ化"
|
||||
"group_by_letter_label": "頭文字でグループ化",
|
||||
"sort_by_last_name_label": "姓で並べ替え",
|
||||
"sort_by_last_name_description": "家族が並んで表示されるように、連絡先一覧を姓の順に並べ替えます"
|
||||
},
|
||||
"downloads": {
|
||||
"title": "ダウンロード",
|
||||
@@ -1963,6 +2002,7 @@
|
||||
"actions": "アクション",
|
||||
"add_action": "アクションを追加",
|
||||
"stop_processing": "以降のルールの処理を停止",
|
||||
"include_spam": "迷惑メールと判定されたメッセージも移動する",
|
||||
"attachment_type_placeholder": "例: pdf, doc, jpg",
|
||||
"value_placeholder_multi": "値(カンマで複数指定可)",
|
||||
"condition_fields": {
|
||||
@@ -2004,6 +2044,8 @@
|
||||
"copy_to_folder": "フォルダーを選択",
|
||||
"forward_to": "転送先メールアドレス",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "コピーを残す",
|
||||
"forward_limit": "このサーバーでの1通あたりの転送上限: {count}。それを超える転送はスキップされます。",
|
||||
"reject_message": "拒否メッセージ",
|
||||
"reject_placeholder": "あなたのメールは拒否されました",
|
||||
"label_name": "ラベル名",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "新しいサブフォルダー...",
|
||||
"new_folder": "新しいフォルダー...",
|
||||
"rename": "名前を変更...",
|
||||
"share": "共有...",
|
||||
"import_email": ".eml または .zip をインポート...",
|
||||
"empty_folder": "フォルダーを空にする",
|
||||
"empty_folder_generic": "フォルダーを空にする",
|
||||
@@ -2234,6 +2277,9 @@
|
||||
"toast_error_delete": "フォルダーを削除できませんでした",
|
||||
"toast_error_delete_has_children": "フォルダーにサブフォルダーがあります。先に削除してください。",
|
||||
"toast_error_delete_has_email": "フォルダーが空ではありません。先に空にしてください。",
|
||||
"delete_with_contents_title": "フォルダーが空ではありません",
|
||||
"delete_with_contents_message": "フォルダー「{name}」にはまだ {count} 件のメッセージがあります。フォルダーとすべてのメッセージを削除しますか?この操作は元に戻せません。",
|
||||
"delete_with_contents_confirm": "フォルダーとメッセージを削除",
|
||||
"placeholder_folder_name": "フォルダー名",
|
||||
"create": "作成",
|
||||
"rename_confirm": "名前を変更"
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "検索にフォーカス",
|
||||
"help": "ショートカットを表示",
|
||||
"refresh": "メールを更新",
|
||||
"select_all": "すべて選択"
|
||||
"select_all": "すべて選択",
|
||||
"global_search": "すべてを検索"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "スレッドの展開/折りたたみ"
|
||||
@@ -2721,7 +2768,10 @@
|
||||
"any_month": "すべての月",
|
||||
"has_email": "メールあり",
|
||||
"has_phone": "電話あり",
|
||||
"has_photo": "写真あり"
|
||||
"has_photo": "写真あり",
|
||||
"sort_by": "並べ替え",
|
||||
"sort_first_name": "名",
|
||||
"sort_last_name": "姓"
|
||||
},
|
||||
"open_categories": "カテゴリを開く"
|
||||
},
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "複製",
|
||||
"today_header": "今日",
|
||||
"tomorrow_header": "明日",
|
||||
"agenda_show_earlier": "以前の予定を表示",
|
||||
"agenda_loading": "読み込み中…",
|
||||
"agenda_range_start": "{date}以降の予定",
|
||||
"agenda_range_end": "{date}までの予定",
|
||||
"export_ics": ".icsとしてエクスポート",
|
||||
"copy_title": "タイトルをコピー",
|
||||
"copy_link": "会議リンクをコピー",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "色"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "空き状況",
|
||||
"free": "空き",
|
||||
"busy": "予定あり",
|
||||
"tentative": "仮",
|
||||
"unknown": "このサーバーのユーザーではありません",
|
||||
"checking": "確認中…"
|
||||
},
|
||||
"title": "参加者",
|
||||
"add": "参加者を追加",
|
||||
"organizer": "主催者",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "すべてのイベント",
|
||||
"cancel": "キャンセル",
|
||||
"save": "保存",
|
||||
"delete": "削除"
|
||||
"delete": "削除",
|
||||
"rsvp_title": "繰り返しイベントに返信",
|
||||
"rsvp_description": "これは繰り返しイベントです。どのイベントに返信しますか?",
|
||||
"respond": "返信"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "リマインダー",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "月カレンダー表示でイベントの時刻を表示する",
|
||||
"show_week_numbers": "週番号を表示",
|
||||
"show_week_numbers_desc": "ミニカレンダーに週番号を表示する",
|
||||
"calendar_free_scroll": "自由スクロール",
|
||||
"calendar_free_scroll_desc": "1つの期間ずつではなく、月・週・日を連続してスクロールします",
|
||||
"enable_tasks": "タスクを有効化",
|
||||
"enable_tasks_desc": "カレンダーにタスク表示を追加して、ToDoを管理します",
|
||||
"show_tasks_on_calendar": "カレンダーにタスクを表示",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "1秒遅延",
|
||||
"hover_preview_delay_2s": "2秒遅延",
|
||||
"hover_preview_off": "無効",
|
||||
"organizer_identity": "招待の主催者アドレス",
|
||||
"organizer_identity_desc": "イベントに人を招待する際に主催者として使用するカレンダーアドレスです。",
|
||||
"organizer_identity_failed": "主催者アドレスを変更できませんでした",
|
||||
"show_birthday_calendar": "誕生日カレンダー",
|
||||
"show_birthday_calendar_desc": "連絡先の誕生日を表示する仮想カレンダーを表示します"
|
||||
},
|
||||
@@ -2947,6 +3017,9 @@
|
||||
"alert_now": "まもなく開始",
|
||||
"alert_in_minutes": "{count}分後",
|
||||
"invitation_sent": "招待を送信しました",
|
||||
"invitations_denied_title": "招待状を送信できません",
|
||||
"invitations_denied": "サーバーが招待状の送信を拒否しました: {reason}。招待状を送らずに予定を保存しますか?",
|
||||
"save_without_invitations": "招待状なしで保存",
|
||||
"rsvp_updated": "回答を更新しました",
|
||||
"rsvp_error": "回答の更新に失敗しました",
|
||||
"event_duplicated": "予定を複製しました",
|
||||
@@ -2975,7 +3048,7 @@
|
||||
"supported_formats": "iCalendar (.ics) ファイルに対応",
|
||||
"url_description": "外部のiCalendar (.ics) フィードのURLを入力してイベントをインポートします。",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "CalDAVおよびiCalendar (.ics) URLに対応",
|
||||
"url_hint": "iCalendar (.ics) URLに対応",
|
||||
"fetch": "取得",
|
||||
"invalid_url": "有効なURLを入力してください",
|
||||
"url_fetch_failed": "URLからカレンダーを取得できませんでした",
|
||||
@@ -3164,6 +3237,8 @@
|
||||
"has_attachment": "添付ファイル",
|
||||
"date_after": "以降",
|
||||
"date_before": "以前",
|
||||
"size_min": "次のサイズ以上 (KB)",
|
||||
"size_max": "次のサイズ未満 (KB)",
|
||||
"starred": "スター付き",
|
||||
"unread": "未読",
|
||||
"read": "既読",
|
||||
@@ -3205,6 +3280,10 @@
|
||||
"hint": "左のメールをクリックして始めるか、ツアーを開始してください。"
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "ドキュメントを編集",
|
||||
"office_loading": "ドキュメントエディターを開いています…",
|
||||
"office_error": "ドキュメントエディターを開けませんでした。",
|
||||
"office_close": "エディターを閉じる",
|
||||
"open_in_new_tab": "新しいタブで開く",
|
||||
"title": "ファイル",
|
||||
"search_placeholder": "ファイルを検索...",
|
||||
@@ -3217,6 +3296,8 @@
|
||||
"rename": "名前を変更",
|
||||
"rename_title": "名前を変更",
|
||||
"new_name": "新しい名前",
|
||||
"name_forbidden_chars": "サーバーでは名前に次の文字を使用できません: {chars}",
|
||||
"name_reserved": "この名前はサーバーで予約されています。",
|
||||
"delete": "削除",
|
||||
"delete_confirm_title": "リソースを削除",
|
||||
"delete_confirm_message": "\"{name}\"を削除してもよろしいですか?この操作は元に戻せません。",
|
||||
@@ -3451,6 +3532,25 @@
|
||||
"unified_mailbox": {
|
||||
"search_unavailable": "統合ビューでは検索を利用できません"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "誰か",
|
||||
"invited": "{name} さんが「{title}」に招待しました",
|
||||
"updated": "{name} さんが「{title}」を更新しました",
|
||||
"cancelled": "{name} さんが「{title}」をキャンセルしました",
|
||||
"untitled": "無題の予定"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "誰か",
|
||||
"shared": "{name} さんが{kind}「{object}」を共有しました",
|
||||
"changed": "{name} さんが{kind}「{object}」へのアクセス権を変更しました",
|
||||
"revoked": "{name} さんが{kind}「{object}」へのアクセス権を削除しました",
|
||||
"object": {
|
||||
"folder": "フォルダー",
|
||||
"calendar": "カレンダー",
|
||||
"address_book": "アドレス帳",
|
||||
"files": "ファイルフォルダー"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "「{name}」を共有",
|
||||
"description": "このサーバー上の他のユーザーまたはグループにアクセス権を付与します。変更はすぐに反映されます。",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "予定のリンクをコピー",
|
||||
"copied": "リンクをコピーしました",
|
||||
"copy_failed": "リンクをコピーできませんでした"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "認証を完了しています…",
|
||||
"completing_message": "接続が確立されるまでお待ちください。",
|
||||
"received_title": "認証を受信しました",
|
||||
"received_message": "接続を開始したタブに戻ることができます。",
|
||||
"failed_title": "認証に失敗しました",
|
||||
"denied": "認証が拒否されました。",
|
||||
"failed_detail": "認証に失敗しました: {error}",
|
||||
"missing_params": "認証コードまたはステートがありません。",
|
||||
"delivery_failed": "認証結果をアプリのタブに渡せませんでした。このウィンドウを閉じて、もう一度お試しください。"
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "すべてを検索",
|
||||
"placeholder": "メール、連絡先、カレンダー、ファイルを検索...",
|
||||
"scope_label": "検索対象",
|
||||
"account_label": "アカウント",
|
||||
"scope_all": "すべて",
|
||||
"scope_mail": "メール",
|
||||
"scope_contacts": "連絡先",
|
||||
"scope_calendar": "カレンダー",
|
||||
"scope_files": "ファイル",
|
||||
"all_accounts": "すべてのアカウント",
|
||||
"show_all": "すべての結果を表示",
|
||||
"no_results": "「{query}」に一致する結果はありません",
|
||||
"server_hint": "完全な単語はサーバーを検索します。部分一致は読み込み済みの項目に適用されます",
|
||||
"searching": "検索中...",
|
||||
"account_error": "{account} を検索できませんでした",
|
||||
"recent_searches": "最近の検索",
|
||||
"recurring": "繰り返し",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "すべてのアカウントのメール、連絡先、予定、ファイルを検索できます。from:、subject:、has:attachment、after:2026-01-01 なども試せます。",
|
||||
"flat_by_date": "日付順のフラット表示",
|
||||
"grouped": "グループ表示",
|
||||
"load_more": "さらに読み込む",
|
||||
"preview_empty": "プレビューする結果を選択してください",
|
||||
"open_full": "開く",
|
||||
"view_label": "表示"
|
||||
}
|
||||
}
|
||||
|
||||
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "외 {count}명",
|
||||
"draft": "임시보관",
|
||||
"starred": "별표 표시됨",
|
||||
"pinned": "고정됨",
|
||||
"replied": "답장함",
|
||||
"forwarded": "전달함",
|
||||
"has_attachment": "첨부 파일 있음",
|
||||
"conversations_count": "전체 {total}개 중 {count}개의 대화",
|
||||
"conversations_count_plus": "{count}개 이상의 대화",
|
||||
"conversations_count_simple": "{count}개의 대화",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "전달",
|
||||
"delete": "삭제",
|
||||
"archive": "보관",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "별표 달기",
|
||||
"unstar": "별표 해제",
|
||||
"mark_unread": "읽지 않은 상태로 표시",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "이동",
|
||||
"print": "인쇄",
|
||||
"view_source": "원본 보기",
|
||||
|
||||
"fullscreen": "전체 화면",
|
||||
|
||||
"exit_fullscreen": "전체 화면 종료",
|
||||
"export_email": ".eml 파일로 내보내기",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "첨부 파일로 전달",
|
||||
"import_email": ".eml 또는 .zip 가져오기",
|
||||
"keyboard_shortcuts": "단축키 (?)",
|
||||
"email_source": "메일 원본",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "읽음 확인 요청됨 (클릭하여 해제)",
|
||||
"read_receipt_off": "읽음 확인 요청",
|
||||
"dsn_on": "배달 알림 요청됨 (클릭하여 해제)",
|
||||
"dsn_off": "배달 알림 요청",
|
||||
"format_plain_text": "일반 텍스트로 전환",
|
||||
"format_rich_text": "서식 있는 텍스트(HTML)로 전환",
|
||||
"require_tls_on": "암호화 전송 필수 (클릭하여 해제)",
|
||||
"require_tls_off": "암호화 전송 필수 (TLS)",
|
||||
"new_message": "새 메시지",
|
||||
"signature_edit_hint": "서명을 편집하려면 두 번 클릭하세요",
|
||||
"reply": "답장",
|
||||
@@ -631,11 +640,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "재정의",
|
||||
"toggle_on": "재정의 취소",
|
||||
"toggle_tooltip": "보낸 사람 이름과 주소를 자유롭게 편집하세요. 메일은 여전히 사용자의 ID를 통해 전송되며 - 표시되는 보낸 사람 헤더만 변경됩니다.",
|
||||
"toggle_tooltip": "보낸 사람 이름과 주소를 자유롭게 편집하세요. 메일은 여전히 사용자의 ID를 통해 전송됩니다.",
|
||||
"name_label": "보낸 사람 이름",
|
||||
"name_placeholder": "이름",
|
||||
"email_label": "보낸 사람 이메일 주소",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "서버가 이 주소를 봉투 발신자로 허용하지 않으면 대신 {identity}이(가) 사용되며, 수신자는 Return-Path 헤더에서 이 주소를 볼 수 있습니다."
|
||||
},
|
||||
"use_template": "템플릿",
|
||||
"save_as_template": "템플릿으로 저장",
|
||||
@@ -648,6 +658,8 @@
|
||||
"upload_progress": "{uploaded} / {total} 업로드 중",
|
||||
"upload_cancel": "업로드 취소",
|
||||
"upload_failed": "{filename} 파일 업로드에 실패했어요",
|
||||
"attachment_too_large": "\"{name}\"이(가) 서버 허용 크기보다 커요(파일당 {max})",
|
||||
"attachments_total_too_large": "첨부 파일이 서버의 메시지당 한도({max})를 넘어요",
|
||||
"drop_files": "첨부할 파일을 여기에 놓아주세요",
|
||||
"show_less": "간략히 보기",
|
||||
"send_failed": "메일 전송에 실패했어요",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "열 삭제",
|
||||
"toggle_header_row": "머리글 행 전환",
|
||||
"delete_table": "표 삭제",
|
||||
"pick_size": "크기 선택"
|
||||
"pick_size": "크기 선택",
|
||||
"font_size": "글자 크기",
|
||||
"font_size_default": "기본값",
|
||||
"background_color": "배경색",
|
||||
"remove_background_color": "배경색 제거"
|
||||
},
|
||||
"send_filing_warning": "보냈지만 전송 후 정리에 실패했습니다. 오래된 임시 보관 메일이 남아 있을 수 있습니다."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "메일이 전송되었어요",
|
||||
"email_deleted": "메일이 삭제되었어요",
|
||||
"email_archived": "메일이 보관되었어요",
|
||||
"emails_deleted": "{count, plural, other {이메일 #개 삭제됨}}",
|
||||
"emails_archived": "{count, plural, other {이메일 #개 보관됨}}",
|
||||
"error_archiving": "이메일을 보관하지 못했습니다",
|
||||
"error_updating": "이메일을 업데이트하지 못했습니다",
|
||||
"email_starred": "별표가 추가되었어요",
|
||||
"email_unstarred": "별표가 해제되었어요",
|
||||
"email_marked_read": "읽은 상태로 변경되었어요",
|
||||
@@ -1143,7 +1163,9 @@
|
||||
"enabled": "이메일 알림",
|
||||
"enabled_desc": "새 이메일이 도착하면 알림을 보여줘요",
|
||||
"sound": "알림음",
|
||||
"sound_desc": "새 이메일이 도착하면 소리로 알려줘요"
|
||||
"sound_desc": "새 이메일이 도착하면 소리로 알려줘요",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "캘린더 알림",
|
||||
@@ -1172,6 +1194,7 @@
|
||||
"status_busy": "처리 중…",
|
||||
"status_inactive": "이 기기에서 활성화되지 않음",
|
||||
"status_unsupported": "이 브라우저는 Web Push를 지원하지 않습니다",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"title": "백그라운드 알림",
|
||||
"devices_title": "등록된 기기",
|
||||
"devices_desc": "이 계정의 백그라운드 알림을 받도록 등록된 모든 기기입니다. 해지하면 해당 기기는 즉시 수신을 멈추니다.",
|
||||
@@ -1238,6 +1261,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "폴더 전환 시 검색 지우기",
|
||||
"description": "폴더(받은편지함, 보낸편지함 등)를 클릭하면 활성 검색을 지우고 해당 폴더를 표시합니다. 꺼져 있으면 검색이 유지되고 이동한 폴더에서 다시 실행됩니다."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "기본적으로 읽음 확인 요청",
|
||||
"description": "새 메시지를 작성할 때 읽음 확인 요청을 미리 켭니다."
|
||||
@@ -1321,6 +1348,12 @@
|
||||
"label": "답장 시 보내는 사람 자동 선택",
|
||||
"description": "답장할 때 메일을 받았던 주소로 보내는 사람을 자동으로 변경해요"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "수신 주소 일치 방식",
|
||||
"description": "어떤 수신 주소를 내 주소로 간주할지 정합니다. '정확한 주소만'은 설정된 ID 중에서만 선택합니다. '같은 도메인'은 ID 도메인의 다른 주소도 캐치올 별칭으로 간주하여 보낸 사람 헤더를 해당 주소로 바꿉니다. 해당 주소가 배포 목록이라면 정확한 주소를 선택하세요.",
|
||||
"exact": "정확한 주소만",
|
||||
"domain": "내 도메인의 모든 주소"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "서명 위치",
|
||||
"description": "답장과 전달에서 서명을 삽입할 위치. 인용된 텍스트 위에 두면 답장의 마무리처럼 자연스럽게 읽히고, 아래에 두면 원본 메시지가 이어져 보입니다.",
|
||||
@@ -1746,7 +1779,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "종료일은 시작일보다 나중이어야 해요",
|
||||
"start_in_past": "시작일이 이미 지났어요",
|
||||
"empty_body": "메시지 내용이 비어있어요. 상대방에게 빈 메일이 가게 돼요"
|
||||
"empty_body": "메시지 내용이 비어있어요. 상대방에게 빈 메일이 가게 돼요",
|
||||
"subject_too_long": "제목이 서버에 비해 너무 깁니다(최대 {max}바이트).",
|
||||
"body_too_long": "메시지가 서버에 비해 너무 깁니다(서식 포함 최대 {max}바이트)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1763,6 +1798,8 @@
|
||||
"role_trash": "휴지통",
|
||||
"role_junk": "스팸/정크함",
|
||||
"role_archive": "보관함",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "없음",
|
||||
"create_folder": "폴더 만들기",
|
||||
"create_subfolder": "하위 폴더 만들기",
|
||||
@@ -1882,7 +1919,9 @@
|
||||
"categories_description": "연락처 카테고리 이름 변경",
|
||||
"no_categories": "카테고리를 찾을 수 없음",
|
||||
"group_by_letter_description": "연락처 목록에 알파벳순 섹션 헤더 표시",
|
||||
"group_by_letter_label": "첫 글자로 그룹화"
|
||||
"group_by_letter_label": "첫 글자로 그룹화",
|
||||
"sort_by_last_name_label": "성으로 정렬",
|
||||
"sort_by_last_name_description": "가족 구성원이 함께 표시되도록 연락처 목록을 성 기준으로 정렬합니다"
|
||||
},
|
||||
"downloads": {
|
||||
"title": "다운로드",
|
||||
@@ -1963,6 +2002,7 @@
|
||||
"actions": "동작",
|
||||
"add_action": "동작 추가",
|
||||
"stop_processing": "이후 규칙 무시하기",
|
||||
"include_spam": "스팸으로 표시된 메시지도 이동",
|
||||
"attachment_type_placeholder": "예: pdf, doc, jpg",
|
||||
"value_placeholder_multi": "값 (쉼표로 여러 개 구분)",
|
||||
"condition_fields": {
|
||||
@@ -2004,6 +2044,8 @@
|
||||
"copy_to_folder": "폴더 선택",
|
||||
"forward_to": "전달할 이메일 주소",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "사본 보관",
|
||||
"forward_limit": "이 서버의 메시지당 전달 한도: {count}. 초과된 전달은 건너뜁니다.",
|
||||
"reject_message": "거부 메시지",
|
||||
"reject_placeholder": "메일 수신이 거부되었습니다",
|
||||
"label_name": "태그 이름",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "새 하위 폴더...",
|
||||
"new_folder": "새 폴더...",
|
||||
"rename": "이름 바꾸기...",
|
||||
"share": "공유...",
|
||||
"import_email": ".eml 또는 .zip 가져오기...",
|
||||
"empty_folder": "폴더 비우기",
|
||||
"empty_folder_generic": "폴더 비우기",
|
||||
@@ -2234,6 +2277,9 @@
|
||||
"toast_error_delete": "폴더를 삭제하지 못했습니다",
|
||||
"toast_error_delete_has_children": "폴더에 하위 폴더가 있습니다. 먼저 제거하세요.",
|
||||
"toast_error_delete_has_email": "폴더가 비어 있지 않습니다. 먼저 비우세요.",
|
||||
"delete_with_contents_title": "폴더가 비어 있지 않습니다",
|
||||
"delete_with_contents_message": "\"{name}\" 폴더에 아직 {count}개의 메시지가 있습니다. 폴더와 모든 메시지를 삭제할까요? 이 작업은 취소할 수 없습니다.",
|
||||
"delete_with_contents_confirm": "폴더와 메시지 삭제",
|
||||
"placeholder_folder_name": "폴더 이름",
|
||||
"create": "만들기",
|
||||
"rename_confirm": "이름 바꾸기"
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "검색창 이동",
|
||||
"help": "단축키 보기",
|
||||
"refresh": "메일 새로고침",
|
||||
"select_all": "모두 선택"
|
||||
"select_all": "모두 선택",
|
||||
"global_search": "전체 검색"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "대화 펼치기/접기"
|
||||
@@ -2721,7 +2768,10 @@
|
||||
"any_month": "모든 달",
|
||||
"has_email": "이메일 있음",
|
||||
"has_phone": "전화번호 있음",
|
||||
"has_photo": "사진 있음"
|
||||
"has_photo": "사진 있음",
|
||||
"sort_by": "정렬 기준",
|
||||
"sort_first_name": "이름",
|
||||
"sort_last_name": "성"
|
||||
},
|
||||
"open_categories": "카테고리 열기"
|
||||
},
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "복제",
|
||||
"today_header": "오늘",
|
||||
"tomorrow_header": "내일",
|
||||
"agenda_show_earlier": "이전 일정 보기",
|
||||
"agenda_loading": "불러오는 중…",
|
||||
"agenda_range_start": "{date}부터의 일정",
|
||||
"agenda_range_end": "{date}까지의 일정",
|
||||
"export_ics": ".ics로 내보내기",
|
||||
"copy_title": "제목 복사",
|
||||
"copy_link": "회의 링크 복사",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "색상"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "일정 상태",
|
||||
"free": "가능",
|
||||
"busy": "바쁨",
|
||||
"tentative": "미정",
|
||||
"unknown": "이 서버의 사용자가 아님",
|
||||
"checking": "확인 중…"
|
||||
},
|
||||
"title": "참석자",
|
||||
"add": "참석자 추가",
|
||||
"organizer": "주최자",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "모든 일정",
|
||||
"cancel": "취소",
|
||||
"save": "저장",
|
||||
"delete": "삭제"
|
||||
"delete": "삭제",
|
||||
"rsvp_title": "반복 일정에 응답",
|
||||
"rsvp_description": "반복되는 일정이에요. 어떤 일정에 응답할까요?",
|
||||
"respond": "응답"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "알림",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "월간 캘린더에서도 일정 시간을 표시해요",
|
||||
"show_week_numbers": "주 차 번호 표시",
|
||||
"show_week_numbers_desc": "미니 캘린더에 1년 중 몇 주 차인지 표시해요",
|
||||
"calendar_free_scroll": "자유 스크롤",
|
||||
"calendar_free_scroll_desc": "한 번에 한 기간씩 보는 대신 월, 주, 일을 연속으로 스크롤합니다",
|
||||
"enable_tasks": "할 일 사용",
|
||||
"enable_tasks_desc": "캘린더에 할 일을 관리할 수 있는 메뉴를 추가해요",
|
||||
"show_tasks_on_calendar": "캘린더에 할 일 표시",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "1초 지연",
|
||||
"hover_preview_delay_2s": "2초 지연",
|
||||
"hover_preview_off": "사용 안 함",
|
||||
"organizer_identity": "초대 주최자 주소",
|
||||
"organizer_identity_desc": "이벤트에 사람들을 초대할 때 주최자로 사용되는 캘린더 주소입니다.",
|
||||
"organizer_identity_failed": "주최자 주소를 변경하지 못했습니다",
|
||||
"show_birthday_calendar": "연락처 생일 캘린더",
|
||||
"show_birthday_calendar_desc": "연락처에 등록된 생일을 가상 캘린더로 보여줘요"
|
||||
},
|
||||
@@ -2947,6 +3017,9 @@
|
||||
"alert_now": "지금 시작해요",
|
||||
"alert_in_minutes": "{count}분 후 시작",
|
||||
"invitation_sent": "초대장이 전송되었어요",
|
||||
"invitations_denied_title": "초대장을 보낼 수 없어요",
|
||||
"invitations_denied": "서버가 초대장 발송을 거부했어요: {reason}. 초대장 없이 일정을 저장할까요?",
|
||||
"save_without_invitations": "초대장 없이 저장",
|
||||
"rsvp_updated": "응답이 업데이트되었어요",
|
||||
"rsvp_error": "응답을 업데이트하지 못했어요",
|
||||
"event_duplicated": "일정이 복제되었어요",
|
||||
@@ -2975,7 +3048,7 @@
|
||||
"supported_formats": "iCalendar (.ics) 파일을 지원해요",
|
||||
"url_description": "외부 iCalendar (.ics) 피드의 URL을 입력해서 일정을 가져와요.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "CalDAV 및 iCalendar (.ics) URL을 지원해요",
|
||||
"url_hint": "iCalendar (.ics) URL을 지원해요",
|
||||
"fetch": "가져오기",
|
||||
"invalid_url": "올바른 URL을 입력해 주세요",
|
||||
"url_fetch_failed": "URL에서 캘린더를 가져오지 못했어요",
|
||||
@@ -3164,6 +3237,8 @@
|
||||
"has_attachment": "첨부파일 있음",
|
||||
"date_after": "이후",
|
||||
"date_before": "이전",
|
||||
"size_min": "다음 크기 이상 (KB)",
|
||||
"size_max": "다음 크기 미만 (KB)",
|
||||
"starred": "별표 있음",
|
||||
"unread": "읽지 않음",
|
||||
"read": "읽음",
|
||||
@@ -3205,6 +3280,10 @@
|
||||
"hint": "왼쪽에서 메일을 클릭해서 시작하거나, 아래의 둘러보기를 클릭해 보세요."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "문서 편집",
|
||||
"office_loading": "문서 편집기를 여는 중…",
|
||||
"office_error": "문서 편집기를 열 수 없습니다.",
|
||||
"office_close": "편집기 닫기",
|
||||
"open_in_new_tab": "새 탭에서 열기",
|
||||
"title": "파일",
|
||||
"search_placeholder": "파일 검색...",
|
||||
@@ -3217,6 +3296,8 @@
|
||||
"rename": "이름 바꾸기",
|
||||
"rename_title": "이름 바꾸기",
|
||||
"new_name": "새 이름",
|
||||
"name_forbidden_chars": "서버에서 이름에 다음 문자를 허용하지 않습니다: {chars}",
|
||||
"name_reserved": "이 이름은 서버에서 예약되어 있습니다.",
|
||||
"delete": "삭제",
|
||||
"delete_confirm_title": "항목 삭제",
|
||||
"delete_confirm_message": "정말 \"{name}\"을(를) 삭제할까요? 이 작업은 되돌릴 수 없어요.",
|
||||
@@ -3451,6 +3532,25 @@
|
||||
"unified_mailbox": {
|
||||
"search_unavailable": "통합 보기에서는 검색을 사용할 수 없습니다"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "누군가",
|
||||
"invited": "{name}님이 \"{title}\"에 초대했습니다",
|
||||
"updated": "{name}님이 \"{title}\"을(를) 업데이트했습니다",
|
||||
"cancelled": "{name}님이 \"{title}\"을(를) 취소했습니다",
|
||||
"untitled": "제목 없는 일정"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "누군가",
|
||||
"shared": "{name}님이 {kind} \"{object}\"을(를) 공유했습니다",
|
||||
"changed": "{name}님이 {kind} \"{object}\"에 대한 접근 권한을 변경했습니다",
|
||||
"revoked": "{name}님이 {kind} \"{object}\"에 대한 접근 권한을 제거했습니다",
|
||||
"object": {
|
||||
"folder": "폴더",
|
||||
"calendar": "캘린더",
|
||||
"address_book": "주소록",
|
||||
"files": "파일 폴더"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "\"{name}\" 공유",
|
||||
"description": "이 서버의 다른 사용자나 그룹에 액세스 권한을 부여합니다. 변경 사항은 즉시 적용됩니다.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "일정 링크 복사",
|
||||
"copied": "링크가 복사되었습니다",
|
||||
"copy_failed": "링크를 복사하지 못했습니다"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "인증 완료 중…",
|
||||
"completing_message": "연결이 설정되는 동안 잠시 기다려 주세요.",
|
||||
"received_title": "인증 수신됨",
|
||||
"received_message": "연결을 시작했던 탭으로 돌아갈 수 있습니다.",
|
||||
"failed_title": "인증 실패",
|
||||
"denied": "인증이 거부되었습니다.",
|
||||
"failed_detail": "인증 실패: {error}",
|
||||
"missing_params": "인증 코드 또는 상태가 누락되었습니다.",
|
||||
"delivery_failed": "인증 결과를 앱 탭에 전달하지 못했습니다. 이 창을 닫고 다시 시도해 주세요."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "전체 검색",
|
||||
"placeholder": "메일, 연락처, 캘린더, 파일 검색...",
|
||||
"scope_label": "검색 범위",
|
||||
"account_label": "계정",
|
||||
"scope_all": "전체",
|
||||
"scope_mail": "메일",
|
||||
"scope_contacts": "연락처",
|
||||
"scope_calendar": "캘린더",
|
||||
"scope_files": "파일",
|
||||
"all_accounts": "모든 계정",
|
||||
"show_all": "모든 결과 보기",
|
||||
"no_results": "“{query}”에 대한 결과가 없습니다",
|
||||
"server_hint": "완전한 단어는 서버에서 검색되며, 단어 일부는 이미 불러온 항목과 일치합니다",
|
||||
"searching": "검색 중...",
|
||||
"account_error": "{account}을(를) 검색할 수 없습니다",
|
||||
"recent_searches": "최근 검색",
|
||||
"recurring": "반복",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "모든 계정의 메일, 연락처, 일정, 파일을 검색하세요. from:, subject:, has:attachment, after:2026-01-01 도 사용할 수 있습니다.",
|
||||
"flat_by_date": "날짜순 평뱀 보기",
|
||||
"grouped": "그룹 보기",
|
||||
"load_more": "더 불러오기",
|
||||
"preview_empty": "미리 볼 결과를 선택하세요",
|
||||
"open_full": "열기",
|
||||
"view_label": "보기"
|
||||
}
|
||||
}
|
||||
|
||||
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "un vēl {count}",
|
||||
"draft": "Melnraksts",
|
||||
"starred": "Ar zvaigznīti",
|
||||
"pinned": "Piesprausts",
|
||||
"replied": "Atbildēts",
|
||||
"forwarded": "Pārsūtīts",
|
||||
"has_attachment": "Ar pielikumu",
|
||||
"conversations_count": "{count} no {total} sarunām",
|
||||
"conversations_count_plus": "{count}+ sarunas",
|
||||
"conversations_count_simple": "{count} sarunas",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Pārsūtīt",
|
||||
"delete": "Dzēst",
|
||||
"archive": "Arhivēt",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Pievienot zvaigznīti",
|
||||
"unstar": "Noņemt zvaigznīti",
|
||||
"mark_unread": "Atzīmēt kā nelasītu",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Pārvietot",
|
||||
"print": "Drukāt",
|
||||
"view_source": "Skatīt avota kodu",
|
||||
|
||||
"fullscreen": "Pilnekrāna režīms",
|
||||
|
||||
"exit_fullscreen": "Iziet no pilnekrāna režīma",
|
||||
"export_email": "Eksportēt kā .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Pārsūtīt kā pielikumu",
|
||||
"import_email": "Importēt .eml vai .zip",
|
||||
"keyboard_shortcuts": "Īsinājumtaustiņi (?)",
|
||||
"email_source": "Vēstules avota kods",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Pieprasīts lasīšanas apstiprinājums (noklikšķiniet, lai atspējotu)",
|
||||
"read_receipt_off": "Pieprasīt lasīšanas apstiprinājumu",
|
||||
"dsn_on": "Piegādes paziņojums pieprasīts (noklikšķiniet, lai atspējotu)",
|
||||
"dsn_off": "Pieprasīt piegādes paziņojumu",
|
||||
"format_plain_text": "Pārslēgt uz vienkāršu tekstu",
|
||||
"format_rich_text": "Pārslēgt uz formatētu tekstu (HTML)",
|
||||
"require_tls_on": "Nepieciešama šifrēta piegāde (noklikšķiniet, lai atspējotu)",
|
||||
"require_tls_off": "Pieprasīt šifrētu piegādi (TLS)",
|
||||
"new_message": "Jauns ziņojums",
|
||||
"signature_edit_hint": "Veiciet dubultklikšķi, lai rediģētu parakstu",
|
||||
"reply": "Atbildēt",
|
||||
@@ -631,11 +640,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Pārrakstīt",
|
||||
"toggle_on": "Atcelt pārrakstīšanu",
|
||||
"toggle_tooltip": "Brīvi rediģējiet sūtītāja vārdu un adresi. Pasts joprojām tiek sūtīts caur jūsu identitāti - mainās tikai redzamais No galvenes ieraksts.",
|
||||
"toggle_tooltip": "Brīvi rediģējiet sūtītāja vārdu un adresi. Pasts joprojām tiek sūtīts caur jūsu identitāti.",
|
||||
"name_label": "Sūtītāja vārds",
|
||||
"name_placeholder": "Vārds",
|
||||
"email_label": "Sūtītāja e-pasta adrese",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "Ja jūsu serveris nepieņem šo adresi kā aploksnes sūtītāju, tās vietā tiek izmantota {identity}, un adresāti to var redzēt galvenē Return-Path."
|
||||
},
|
||||
"use_template": "Veidne",
|
||||
"save_as_template": "Saglabāt kā veidni",
|
||||
@@ -648,6 +658,8 @@
|
||||
"upload_progress": "Augšupielāde {uploaded} / {total}",
|
||||
"upload_cancel": "Atcelt augšupielādi",
|
||||
"upload_failed": "Neizdevās augšupielādēt {filename}",
|
||||
"attachment_too_large": "“{name}” ir lielāks, nekā serveris atļauj ({max} vienam failam)",
|
||||
"attachments_total_too_large": "Pielikumi pārsniegtu servera ierobežojumu – {max} vienai ziņai",
|
||||
"drop_files": "Velciet failus šeit, lai pievienotu",
|
||||
"show_less": "Rādīt mazāk",
|
||||
"send_failed": "Neizdevās nosūtīt vēstuli",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Dzēst kolonnu",
|
||||
"toggle_header_row": "Pārslēgt galvenes rindu",
|
||||
"delete_table": "Dzēst tabulu",
|
||||
"pick_size": "Izvēlēties izmēru"
|
||||
"pick_size": "Izvēlēties izmēru",
|
||||
"font_size": "Fonta izmērs",
|
||||
"font_size_default": "Noklusējums",
|
||||
"background_color": "Fona krāsa",
|
||||
"remove_background_color": "Noņemt fona krāsu"
|
||||
},
|
||||
"send_filing_warning": "Nosūtīts - bet pēcapstrāde neizdevās, var palikt novecojis melnraksts."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "Vēstule veiksmīgi nosūtīta",
|
||||
"email_deleted": "Vēstule izdzēsta",
|
||||
"email_archived": "Vēstule arhivēta",
|
||||
"emails_deleted": "{count, plural, zero {# e-pasti dzēsti} one {# e-pasts dzēsts} other {# e-pasti dzēsti}}",
|
||||
"emails_archived": "{count, plural, zero {# e-pasti arhivēti} one {# e-pasts arhivēts} other {# e-pasti arhivēti}}",
|
||||
"error_archiving": "Neizdevās arhivēt e-pastu",
|
||||
"error_updating": "Neizdevās atjaunināt e-pastu",
|
||||
"email_starred": "Vēstulei pievienota zvaigznīte",
|
||||
"email_unstarred": "Zvaigznīte noņemta",
|
||||
"email_marked_read": "Vēstule atzīmēta kā izlasīta",
|
||||
@@ -1143,7 +1163,9 @@
|
||||
"enabled": "E-pasta paziņojumi",
|
||||
"enabled_desc": "Rādīt paziņojumus, saņemot jaunas vēstules",
|
||||
"sound": "Paziņojuma skaņa",
|
||||
"sound_desc": "Atskaņot skaņas signālu, saņemot jaunas vēstules"
|
||||
"sound_desc": "Atskaņot skaņas signālu, saņemot jaunas vēstules",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Kalendāra paziņojumi",
|
||||
@@ -1172,6 +1194,7 @@
|
||||
"status_busy": "Notiek darbs…",
|
||||
"status_inactive": "Nav iespējots šajā ierīcē",
|
||||
"status_unsupported": "Šī pārlūkprogramma neatbalsta Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"title": "Fona paziņojumi",
|
||||
"devices_title": "Reģistrētās ierīces",
|
||||
"devices_desc": "Visas ierīces, kas reģistrētas šī konta fona paziņojumu saņemšanai. Atsaucot ierīci, tā nekavējoties pārstāj tās saņemt.",
|
||||
@@ -1238,6 +1261,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Notīrīt meklēšanu, mainot mapi",
|
||||
"description": "Noklikšķinot uz mapes (Iesūtne, Nosūtītie…), aktīvā meklēšana tiek notīrīta un tiek atvērta šī mape. Kad izslēgts, meklēšana paliek un tiek atkārtota izvēlētajā mapē."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Pēc noklusējuma pieprasīt lasīšanas apstiprinājumus",
|
||||
"description": "Sastādot jaunu ziņojumu, iepriekš ieslēgt lasīšanas apstiprinājuma pieprasījumu."
|
||||
@@ -1316,6 +1343,12 @@
|
||||
"label": "Automātiski izvēlēties atbildes adresi",
|
||||
"description": "Atbildot automātiski izmantot to kontu, uz kuru vēstule tika saņemta"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Saņemšanas adreses atbilstība",
|
||||
"description": "Kuras saņemšanas adreses tiek uzskatītas par jūsu. „Tikai precīza adrese“ izvēlas vienīgi kādu no jūsu konfigurētajām identitātēm; „tas pats domēns“ arī jebkuru citu adresi jūsu identitāšu domēnos uzskata par catch-all aizstājvārdu un pārraksta ar to galveni „No“. Izvēlieties precīzu adresi, ja šīs adreses ir izplatīšanas saraksti.",
|
||||
"exact": "Tikai precīza adrese",
|
||||
"domain": "Jebkura adrese manos domēnos"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Paraksta novietojums",
|
||||
"description": "Kur ievietot jūsu parakstu atbildēs un pārsūtīšanā. Virs citētā teksta tas dabiski lasās kā atbildes noslēgums; zem tā saglabā oriģinālo ziņojumu vienkopus.",
|
||||
@@ -1746,7 +1779,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "Beigu datumam jābūt vēlāk par sākuma datumu",
|
||||
"start_in_past": "Sākuma datums ir pagātnē",
|
||||
"empty_body": "Ziņojuma teksts ir tukšs - saņēmēji saņems tukšu atbildi"
|
||||
"empty_body": "Ziņojuma teksts ir tukšs - saņēmēji saņems tukšu atbildi",
|
||||
"subject_too_long": "Temats serverim ir pārāk garš (ne vairāk kā {max} baiti).",
|
||||
"body_too_long": "Ziņa serverim ir pārāk gara (ne vairāk kā {max} baiti, ieskaitot formatējumu)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1763,6 +1798,8 @@
|
||||
"role_trash": "Atkritne",
|
||||
"role_junk": "Mēstules",
|
||||
"role_archive": "Arhīvs",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Nav",
|
||||
"create_folder": "Izveidot mapi",
|
||||
"create_subfolder": "Izveidot apakšmapi",
|
||||
@@ -1882,7 +1919,9 @@
|
||||
"categories_description": "Pārdēvēt kontaktu kategorijas",
|
||||
"no_categories": "Kategorijas nav atrastas",
|
||||
"group_by_letter_description": "Rādīt alfabētiskos sadaļu virsrakstus kontaktu sarakstā",
|
||||
"group_by_letter_label": "Grupēt pēc pirmā burta"
|
||||
"group_by_letter_label": "Grupēt pēc pirmā burta",
|
||||
"sort_by_last_name_label": "Kārtot pēc uzvārda",
|
||||
"sort_by_last_name_description": "Kārtot kontaktu sarakstu pēc uzvārda, lai ģimenes locekļi būtu kopā"
|
||||
},
|
||||
"downloads": {
|
||||
"title": "Lejupielādes",
|
||||
@@ -1963,6 +2002,7 @@
|
||||
"actions": "Darbības",
|
||||
"add_action": "Pievienot darbību",
|
||||
"stop_processing": "Pārtraukt nākamo noteikumu apstrādi",
|
||||
"include_spam": "Pārvietot arī ziņas, kas atzīmētas kā mēstules",
|
||||
"attachment_type_placeholder": "piem. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Vērtība (vairākas atdalītas ar komatu)",
|
||||
"condition_fields": {
|
||||
@@ -2004,6 +2044,8 @@
|
||||
"copy_to_folder": "Izvēlēties mapi",
|
||||
"forward_to": "Pārsūtīt uz e-pastu",
|
||||
"forward_placeholder": "lietotajs@piemers.lv",
|
||||
"forward_keep_copy": "Saglabāt kopiju",
|
||||
"forward_limit": "Pārsūtīšanas ierobežojums vienai ziņai šajā serverī: {count}. Papildu pārsūtīšanas tiek izlaistas.",
|
||||
"reject_message": "Noraidīšanas ziņojums",
|
||||
"reject_placeholder": "Jūsu e-pasts tika noraidīts",
|
||||
"label_name": "Taga nosaukums",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Jauna apakšmape...",
|
||||
"new_folder": "Jauna mape...",
|
||||
"rename": "Pārsaukt...",
|
||||
"share": "Kopīgot...",
|
||||
"import_email": "Importēt .eml vai .zip...",
|
||||
"empty_folder": "Iztukšot mapi",
|
||||
"empty_folder_generic": "Iztukšot mapi",
|
||||
@@ -2234,6 +2277,9 @@
|
||||
"toast_error_delete": "Neizdevās dzēst mapi",
|
||||
"toast_error_delete_has_children": "Mapei ir apakšmapes. Vispirms noņemiet tās.",
|
||||
"toast_error_delete_has_email": "Mape nav tukša. Vispirms iztukšojiet to.",
|
||||
"delete_with_contents_title": "Mape nav tukša",
|
||||
"delete_with_contents_message": "Mapē “{name}” joprojām ir {count, plural, one {1 ziņa} other {# ziņas}}. Dzēst mapi un visas tās ziņas? To nevar atsaukt.",
|
||||
"delete_with_contents_confirm": "Dzēst mapi un ziņas",
|
||||
"placeholder_folder_name": "Mapes nosaukums",
|
||||
"create": "Izveidot",
|
||||
"rename_confirm": "Pārsaukt"
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Iet uz meklēšanu",
|
||||
"help": "Rādīt īsinājumtaustiņus",
|
||||
"refresh": "Atsvaidzināt vēstules",
|
||||
"select_all": "Atlasīt visas"
|
||||
"select_all": "Atlasīt visas",
|
||||
"global_search": "Meklēt visā"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Izvērst/sairt sarunu"
|
||||
@@ -2721,7 +2768,10 @@
|
||||
"any_month": "Jebkurš mēnesis",
|
||||
"has_email": "Ar e-pastu",
|
||||
"has_phone": "Ar tālruni",
|
||||
"has_photo": "Ar foto"
|
||||
"has_photo": "Ar foto",
|
||||
"sort_by": "Kārtot pēc",
|
||||
"sort_first_name": "Vārds",
|
||||
"sort_last_name": "Uzvārds"
|
||||
},
|
||||
"open_categories": "Atvērt kategorijas"
|
||||
},
|
||||
@@ -2757,6 +2807,10 @@
|
||||
"duplicate": "Dublēt",
|
||||
"today_header": "Šodien",
|
||||
"tomorrow_header": "Rīt",
|
||||
"agenda_show_earlier": "Rādīt agrākos notikumus",
|
||||
"agenda_loading": "Ielādē…",
|
||||
"agenda_range_start": "Notikumi no {date}",
|
||||
"agenda_range_end": "Notikumi līdz {date}",
|
||||
"export_ics": "Eksportēt kā .ics",
|
||||
"copy_title": "Kopēt nosaukumu",
|
||||
"copy_link": "Kopēt sapulces saiti",
|
||||
@@ -2792,6 +2846,14 @@
|
||||
"color": "Krāsa"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Pieejamība",
|
||||
"free": "Pieejams",
|
||||
"busy": "Aizņemts",
|
||||
"tentative": "Provizoriski",
|
||||
"unknown": "Nav šī servera lietotājs",
|
||||
"checking": "Pārbauda…"
|
||||
},
|
||||
"title": "Dalībnieki",
|
||||
"add": "Pievienot dalībnieku",
|
||||
"organizer": "Organizators",
|
||||
@@ -2866,7 +2928,10 @@
|
||||
"all_events": "Visus pasākumus",
|
||||
"cancel": "Atcelt",
|
||||
"save": "Saglabāt",
|
||||
"delete": "Dzēst"
|
||||
"delete": "Dzēst",
|
||||
"rsvp_title": "Atbildēt uz atkārtotu pasākumu",
|
||||
"rsvp_description": "Šis ir atkārtots pasākums. Uz kurām instancēm attiecas jūsu atbilde?",
|
||||
"respond": "Atbildēt"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Atgādinājums",
|
||||
@@ -2904,6 +2969,8 @@
|
||||
"show_time_in_month_view_desc": "Rādīt pasākuma laiku mēneša kalendārā",
|
||||
"show_week_numbers": "Rādīt nedēļu numurus",
|
||||
"show_week_numbers_desc": "Rādīt nedēļu numurus mini kalendārā",
|
||||
"calendar_free_scroll": "Brīva ritināšana",
|
||||
"calendar_free_scroll_desc": "Nepārtraukti ritināt cauri mēnešiem, nedēļām un dienām, nevis pa vienam periodam",
|
||||
"enable_tasks": "Iespējot uzdevumus",
|
||||
"enable_tasks_desc": "Rādīt uzdevumu sadaļu kalendārā",
|
||||
"show_tasks_on_calendar": "Rādīt uzdevumus kalendārā",
|
||||
@@ -2915,6 +2982,9 @@
|
||||
"hover_preview_delay_1s": "1 sekundes aizture",
|
||||
"hover_preview_delay_2s": "2 sekunžu aizture",
|
||||
"hover_preview_off": "Izslēgts",
|
||||
"organizer_identity": "Organizēt ielūgumus kā",
|
||||
"organizer_identity_desc": "Kalendāra adrese, ko izmanto kā organizatoru, uzaicinot cilvēkus uz notikumiem.",
|
||||
"organizer_identity_failed": "Neizdevās mainīt organizatora adresi",
|
||||
"show_birthday_calendar": "Kontaktu dzimšanas dienu kalendārs",
|
||||
"show_birthday_calendar_desc": "Rādīt virtuālu kalendāru ar kontaktu dzimšanas dienām"
|
||||
},
|
||||
@@ -2946,6 +3016,9 @@
|
||||
"alert_now": "Sākas tagad",
|
||||
"alert_in_minutes": "Pēc {count} min",
|
||||
"invitation_sent": "Uzaicinājumi nosūtīti",
|
||||
"invitations_denied_title": "Ielūgumus nevar nosūtīt",
|
||||
"invitations_denied": "Serveris atteicās nosūtīt ielūgumus: {reason}. Saglabāt notikumu, tos nenosūtot?",
|
||||
"save_without_invitations": "Saglabāt bez ielūgumiem",
|
||||
"rsvp_updated": "Atbilde atjaunināta",
|
||||
"rsvp_error": "Neizdevās atjaunināt atbildi",
|
||||
"event_duplicated": "Pasākums dublēts",
|
||||
@@ -2974,7 +3047,7 @@
|
||||
"supported_formats": "Atbalsta iCalendar (.ics) failus",
|
||||
"url_description": "Ievadiet ārējas iCalendar plūsmas URL.",
|
||||
"url_placeholder": "https://piemers.lv/kalendars.ics",
|
||||
"url_hint": "Atbalsta CalDAV un iCalendar (.ics) saites",
|
||||
"url_hint": "Atbalsta iCalendar (.ics) saites",
|
||||
"fetch": "Iegūt",
|
||||
"invalid_url": "Ievadiet derīgu URL",
|
||||
"url_fetch_failed": "Neizdevās iegūt kalendāru no saites",
|
||||
@@ -3164,6 +3237,8 @@
|
||||
"has_attachment": "Ar pielikumu",
|
||||
"date_after": "Pēc",
|
||||
"date_before": "Pirms",
|
||||
"size_min": "Lielāks par (KB)",
|
||||
"size_max": "Mazāks par (KB)",
|
||||
"starred": "Ar zvaigznīti",
|
||||
"unread": "Nelasītas",
|
||||
"read": "Izlasītas",
|
||||
@@ -3205,6 +3280,10 @@
|
||||
"hint": "Noklikšķiniet uz jebkuru vēstuli kreisajā pusē, lai sāktu."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Rediģēt dokumentu",
|
||||
"office_loading": "Atver dokumentu redaktoru…",
|
||||
"office_error": "Neizdevās atvērt dokumentu redaktoru.",
|
||||
"office_close": "Aizvērt redaktoru",
|
||||
"open_in_new_tab": "Atvērt jaunā cilnē",
|
||||
"title": "Faili",
|
||||
"search_placeholder": "Meklēt failus...",
|
||||
@@ -3217,6 +3296,8 @@
|
||||
"rename": "Pārdēvēt",
|
||||
"rename_title": "Pārdēvēt",
|
||||
"new_name": "Jauns nosaukums",
|
||||
"name_forbidden_chars": "Serveris neatļauj šīs rakstzīmes nosaukumos: {chars}",
|
||||
"name_reserved": "Šis nosaukums serverī ir rezervēts.",
|
||||
"delete": "Dzēst",
|
||||
"delete_confirm_title": "Dzēst resursu",
|
||||
"delete_confirm_message": "Vai tiešām vēlaties dzēst «{name}»? Šo darbību nevar atcelt.",
|
||||
@@ -3451,6 +3532,25 @@
|
||||
"unified_mailbox": {
|
||||
"search_unavailable": "Meklēšana nav pieejama apvienotajā skatā"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Kāds",
|
||||
"invited": "{name} uzaicināja jūs uz “{title}”",
|
||||
"updated": "{name} atjaunināja “{title}”",
|
||||
"cancelled": "{name} atcēla “{title}”",
|
||||
"untitled": "Notikums bez nosaukuma"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Kāds",
|
||||
"shared": "{name} kopīgoja ar jums {kind} “{object}”",
|
||||
"changed": "{name} mainīja jūsu piekļuvi {kind} “{object}”",
|
||||
"revoked": "{name} noņēma jūsu piekļuvi {kind} “{object}”",
|
||||
"object": {
|
||||
"folder": "mapi",
|
||||
"calendar": "kalendāru",
|
||||
"address_book": "adrešu grāmatu",
|
||||
"files": "failu mapi"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Kopīgot \"{name}\"",
|
||||
"description": "Piešķiriet piekļuvi citiem lietotājiem vai grupām šajā serverī. Izmaiņas stājas spēkā nekavējoties.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Kopēt saiti uz notikumu",
|
||||
"copied": "Saite nokopēta",
|
||||
"copy_failed": "Neizdevās nokopēt saiti"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Pabeidz autorizāciju…",
|
||||
"completing_message": "Lūdzu, uzgaidiet, kamēr tiek izveidots savienojums.",
|
||||
"received_title": "Autorizācija saņemta",
|
||||
"received_message": "Varat atgriezties cilnē, kurā sākāt savienojumu.",
|
||||
"failed_title": "Autorizācija neizdevās",
|
||||
"denied": "Autorizācija tika atteikta.",
|
||||
"failed_detail": "Autorizācija neizdevās: {error}",
|
||||
"missing_params": "Trūkst autorizācijas kods vai stāvoklis.",
|
||||
"delivery_failed": "Autorizācijas rezultātu neizdevās nodot lietotnes cilnei. Aizveriet šo logu un mēģiniet vēlreiz."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Meklēt visā",
|
||||
"placeholder": "Meklēt pastā, kontaktos, kalendārā, failos...",
|
||||
"scope_label": "Meklēt",
|
||||
"account_label": "Konts",
|
||||
"scope_all": "Viss",
|
||||
"scope_mail": "Pasts",
|
||||
"scope_contacts": "Kontakti",
|
||||
"scope_calendar": "Kalendārs",
|
||||
"scope_files": "Faili",
|
||||
"all_accounts": "Visi konti",
|
||||
"show_all": "Rādīt visus rezultātus",
|
||||
"no_results": "Nav rezultātu vaicājumam “{query}”",
|
||||
"server_hint": "Pilni vārdi meklē serverī; vārdu daļas atbilst jau ielādētajiem vienumiem",
|
||||
"searching": "Meklē...",
|
||||
"account_error": "Neizdevās meklēt kontā {account}",
|
||||
"recent_searches": "Pēdējie meklējumi",
|
||||
"recurring": "Period isks",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Meklējiet pastu, kontaktus, notikumus un failus visos kontos. Izmēģiniet from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Plakani pēc datuma",
|
||||
"grouped": "Grupēts",
|
||||
"load_more": "Ielādēt vairāk",
|
||||
"preview_empty": "Izvēlieties rezultātu, lai to priekšskatītu",
|
||||
"open_full": "Atvērt",
|
||||
"view_label": "Skats"
|
||||
}
|
||||
}
|
||||
|
||||
+152
-12
@@ -225,6 +225,10 @@
|
||||
"and_others": "болон {count} бусад",
|
||||
"draft": "Ноорог",
|
||||
"starred": "Одтой",
|
||||
"pinned": "Бэхэлсэн",
|
||||
"replied": "Хариулсан",
|
||||
"forwarded": "Дамжуулсан",
|
||||
"has_attachment": "Хавсралттай",
|
||||
"conversations_count": "{total} харилцан ярианы {count}",
|
||||
"conversations_count_plus": "{count}+ харилцан яриа",
|
||||
"conversations_count_simple": "{count} харилцан яриа",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Урагшаа",
|
||||
"delete": "Устгах",
|
||||
"archive": "Архив",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "од",
|
||||
"unstar": "Одгүй болгох",
|
||||
"mark_unread": "Уншаагүй гэж тэмдэглэх",
|
||||
@@ -303,9 +308,7 @@
|
||||
"move": "Хөдөлгөөн",
|
||||
"print": "Хэвлэх",
|
||||
"view_source": "Эх сурвалжийг харах",
|
||||
|
||||
"fullscreen": "Бүтэн дэлгэц",
|
||||
|
||||
"exit_fullscreen": "Бүтэн дэлгэцээс гарах",
|
||||
"export_email": ".eml",
|
||||
"forward_as_attachment": "Хавсралт болгон дамжуулах",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Уншсан баримт хүссэн (идэвхгүй болгохын тулд дарна уу)",
|
||||
"read_receipt_off": "Уншсан баримт хүсэх",
|
||||
"dsn_on": "Хүргэлтийн мэдэгдэл хүссэн (идэвхгүй болгох бол дарна уу)",
|
||||
"dsn_off": "Хүргэлтийн мэдэгдэл хүсэх",
|
||||
"format_plain_text": "Энгийн текст рүү шилжих",
|
||||
"format_rich_text": "Форматтай текст (HTML) рүү шилжих",
|
||||
"require_tls_on": "Шифрлэгдсэн хүргэлт шаардлагатай (идэвхгүй болгох бол дарна уу)",
|
||||
"require_tls_off": "Шифрлэгдсэн хүргэлт шаардах (TLS)",
|
||||
"new_message": "Шинэ мессеж",
|
||||
"signature_edit_hint": "Гарын үсгийг засахын тулд давхар товшино уу",
|
||||
"reply": "Хариулах",
|
||||
@@ -634,11 +643,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Дараах",
|
||||
"toggle_on": "Дараахыг цуцлах",
|
||||
"toggle_tooltip": "From нэр хаягийг чөлөөтэй засварлана. Таны хувийн мэдээллээр шуудан илгээгдсэн хэвээр байна - зөвхөн харагдах \"From\" гарчиг өөрчлөгдөнө.",
|
||||
"toggle_tooltip": "From нэр хаягийг чөлөөтэй засварлана. Таны хувийн мэдээллээр шуудан илгээгдсэн хэвээр байна.",
|
||||
"name_label": "Нэрнээс",
|
||||
"name_placeholder": "Нэр",
|
||||
"email_label": "Имэйл хаягаас",
|
||||
"email_placeholder": "user@example.com"
|
||||
"email_placeholder": "user@example.com",
|
||||
"envelope_notice": "Хэрэв таны сервер энэ хаягийг дугтуйн илгээгчээр хүлээн авахгүй бол оронд нь {identity} ашиглагдах бөгөөд хүлээн авагчид үүнийг Return-Path гарчигт харж болно."
|
||||
},
|
||||
"use_template": "Загвар",
|
||||
"save_as_template": "Загвар болгон хадгалах",
|
||||
@@ -651,6 +661,8 @@
|
||||
"upload_progress": "{uploaded} / {total}",
|
||||
"upload_cancel": "Байршуулалтыг цуцлах",
|
||||
"upload_failed": "{filename}",
|
||||
"attachment_too_large": "\"{name}\" сервер зөвшөөрөхөөс том байна (нэг файлд {max})",
|
||||
"attachments_total_too_large": "Хавсралтууд серверийн нэг захидалд ногдох {max} хязгаараас хэтэрнэ",
|
||||
"drop_files": "Файлуудыг хавсаргах",
|
||||
"show_less": "Бага харуулах",
|
||||
"send_failed": "Имэйл илгээж чадсангүй",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Баганыг устгах",
|
||||
"toggle_header_row": "Толгойн мөрийг сэлгэх",
|
||||
"delete_table": "Хүснэгт устгах",
|
||||
"pick_size": "Хэмжээ сонгох"
|
||||
"pick_size": "Хэмжээ сонгох",
|
||||
"font_size": "Фонтын хэмжээ",
|
||||
"font_size_default": "Үндсэн",
|
||||
"background_color": "Дэвсгэр өнгө",
|
||||
"remove_background_color": "Дэвсгэр өнгийг арилгах"
|
||||
},
|
||||
"send_filing_warning": "Илгээсэн - гэхдээ илгээсний дараах цэвэрлэгээ амжилтгүй болсон тул хуучирсан ноорог үлдэж болзошгүй."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "Имэйл амжилттай илгээгдсэн",
|
||||
"email_deleted": "Имэйл устгасан",
|
||||
"email_archived": "Имэйл архивлагдсан",
|
||||
"emails_deleted": "{count, plural, other {# имэйл устгагдлаа}}",
|
||||
"emails_archived": "{count, plural, other {# имэйл архивлагдлаа}}",
|
||||
"error_archiving": "Имэйлийг архивлаж чадсангүй",
|
||||
"error_updating": "Имэйлийг шинэчилж чадсангүй",
|
||||
"email_starred": "И-мэйл одоор тэмдэглэгдсэн",
|
||||
"email_unstarred": "И-мэйл одыг арилгасан",
|
||||
"email_marked_read": "И-мэйл уншсан гэж тэмдэглэсэн",
|
||||
@@ -1144,6 +1164,7 @@
|
||||
"status_active": "Энэ төхөөрөмжид идэвхтэй байна",
|
||||
"status_inactive": "Энэ төхөөрөмж дээр идэвхжээгүй",
|
||||
"status_unsupported": "Энэ хөтөч Web Push-ийг дэмждэггүй",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"status_busy": "Ажиллаж байна…",
|
||||
"enable": "Идэвхжүүлэх",
|
||||
"reenable": "Дахин бүртгүүлэх",
|
||||
@@ -1181,7 +1202,9 @@
|
||||
"enabled": "И-мэйл мэдэгдэл",
|
||||
"enabled_desc": "Шинэ имэйл ирэх үед мэдэгдлийг харуулах",
|
||||
"sound": "Мэдэгдэлийн дуу",
|
||||
"sound_desc": "Шинэ имэйл ирэх үед аудио дохиог тоглуулах"
|
||||
"sound_desc": "Шинэ имэйл ирэх үед аудио дохиог тоглуулах",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Календарийн мэдэгдэл",
|
||||
@@ -1241,6 +1264,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Хавтас солих үед хайлтыг арилгах",
|
||||
"description": "Хавтас дээр дарахад (Ирсэн, Илгээсэн…) идэвхтэй хайлт арилж, тухайн хавтас нээгдэнэ. Унтраасан үед хайлт хадгалагдаж, шилжсэн хавтаст дахин ажиллана."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Анхдагчаар уншсан баримтыг хүсэх",
|
||||
"description": "Шинэ мессеж бичихдээ уншсан хүлээн авах хүсэлтийг урьдчилан идэвхжүүлнэ."
|
||||
@@ -1324,6 +1351,12 @@
|
||||
"label": "Хүлээн авсан хаягаас хариу бичих",
|
||||
"description": "Хариу бичихдээ мессежийг анх илгээсэн хаягаар нь илгээнэ үү. Эхлээд таних тэмдэгтэй таарах; Домэйн бүх хүргэлтийн хувьд, таны үндсэн таниулбараар дамжуулан илгээхдээ From толгой хэсгийг өөр нэр рүү дахин бичнэ."
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Хүлээн авах хаягийн тохирол",
|
||||
"description": "Хүлээн авах ямар хаягуудыг таных гэж үзэх. „Зөвхөн яг таарах хаяг“ нь зөвхөн таны тохируулсан identity-уудаас сонгоно; „ижил домэйн“ нь таны identity-ийн домэйн дээрх бусад хаягийг ч catch-all нэр болгон үзэж, Илгээгч толгойг тэр хаягаар солино. Эдгээр хаяг нь түгээлтийн жагсаалт бол яг таарах хаягийг сонгоно уу.",
|
||||
"exact": "Зөвхөн яг таарах хаяг",
|
||||
"domain": "Миний домэйн дээрх дурын хаяг"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Гарын үсэг зурах албан тушаал",
|
||||
"description": "Хариулт болон дамжуулалтад гарын үсгээ хаана оруулах вэ. Иш татсан бичвэрийн дээрх хариултыг хаах нь ойлгомжтой; доор нь эх мессежийг залгаж хадгална.",
|
||||
@@ -1753,7 +1786,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "Төгсгөлийн огноо нь эхлэх огнооноос хойш байх ёстой",
|
||||
"start_in_past": "Эхлэх огноо өнгөрсөн байна",
|
||||
"empty_body": "Зурвасны хэсэг хоосон байна - хүлээн авагчид хоосон хариу ирэх болно"
|
||||
"empty_body": "Зурвасны хэсэг хоосон байна - хүлээн авагчид хоосон хариу ирэх болно",
|
||||
"subject_too_long": "Гарчиг сервертэй харьцуулахад хэт урт байна (дээд тал нь {max} байт).",
|
||||
"body_too_long": "Захидал сервертэй харьцуулахад хэт урт байна (форматыг оруулаад дээд тал нь {max} байт)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1770,6 +1805,8 @@
|
||||
"role_trash": "Хогийн сав",
|
||||
"role_junk": "Спам / Хог",
|
||||
"role_archive": "Архив",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Байхгүй",
|
||||
"create_folder": "Хавтас үүсгэх",
|
||||
"create_subfolder": "Дэд хавтас үүсгэх",
|
||||
@@ -1879,6 +1916,8 @@
|
||||
"title": "Харилцах утас",
|
||||
"description": "Харилцагчаа импортлох, экспортлох",
|
||||
"group_by_letter_label": "Эхний үсгээр бүлэглэх",
|
||||
"sort_by_last_name_label": "Овгоор эрэмбэлэх",
|
||||
"sort_by_last_name_description": "Гэр бүлийн гишүүд хамт харагдахаар харилцах хүмүүсийн жагсаалтыг овгоор эрэмбэлэх",
|
||||
"group_by_letter_description": "Харилцах хүмүүсийн жагсаалтад цагаан толгойн үсгийн дарааллаар хэсгийн толгойг харуулах",
|
||||
"import_label": "Харилцагчдыг импортлох",
|
||||
"import_description": "vCard (.vcf) файлаас харилцагчдыг импортлох",
|
||||
@@ -1970,6 +2009,7 @@
|
||||
"actions": "Үйлдэл",
|
||||
"add_action": "Үйлдэл нэмэх",
|
||||
"stop_processing": "Дараагийн дүрмийг боловсруулахаа зогсоох",
|
||||
"include_spam": "Спам гэж тэмдэглэсэн захидлыг мөн зөөх",
|
||||
"attachment_type_placeholder": "жишээ нь pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Утга (олон тоог таслалаар тусгаарласан)",
|
||||
"condition_fields": {
|
||||
@@ -2011,6 +2051,8 @@
|
||||
"copy_to_folder": "Фолдер сонгох",
|
||||
"forward_to": "Имэйл хаяг руу шилжүүлэх",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "Хуулбар хадгалах",
|
||||
"forward_limit": "Энэ сервер дээрх нэг захидлын дамжуулах хязгаар: {count}. Илүү дамжуулалтыг алгасна.",
|
||||
"reject_message": "Татгалзсан мессеж",
|
||||
"reject_placeholder": "Таны имэйл татгалзсан байна",
|
||||
"label_name": "Тагийн нэр",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Шинэ дэд хавтас...",
|
||||
"new_folder": "Шинэ хавтас...",
|
||||
"rename": "Нэр өөрчлөх...",
|
||||
"share": "Хуваалцах...",
|
||||
"import_email": "Импорт .eml эсвэл .zip...",
|
||||
"empty_folder": "Хоосон хавтас",
|
||||
"empty_folder_generic": "Хоосон хавтас",
|
||||
@@ -2236,7 +2279,10 @@
|
||||
"toast_error_rename": "Хавтсын нэрийг өөрчилж чадсангүй",
|
||||
"toast_error_delete": "Хавтсыг устгаж чадсангүй",
|
||||
"toast_error_delete_has_children": "Хавтас дэд хавтастай байна. Эхлээд тэдгээрийг устгана уу.",
|
||||
"toast_error_delete_has_email": "Хавтас хоосон биш байна. Эхлээд үүнийг хоосло."
|
||||
"toast_error_delete_has_email": "Хавтас хоосон биш байна. Эхлээд үүнийг хоосло.",
|
||||
"delete_with_contents_title": "Хавтас хоосон биш байна",
|
||||
"delete_with_contents_message": "\"{name}\" хавтсанд одоо ч {count} зурвас байна. Хавтас болон бүх зурвасыг устгах уу? Үүнийг буцаах боломжгүй.",
|
||||
"delete_with_contents_confirm": "Хавтас болон зурвасыг устгах"
|
||||
},
|
||||
"shortcuts": {
|
||||
"title": "Гарын товчлол",
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Хайлт руу шилжих",
|
||||
"help": "Товчлол харуулах",
|
||||
"refresh": "Имэйл сэргээх",
|
||||
"select_all": "Бүгдийг сонго"
|
||||
"select_all": "Бүгдийг сонго",
|
||||
"global_search": "Бүгдийг хайх"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Хэлхээсийг дэлгэх/хумих"
|
||||
@@ -2722,7 +2769,10 @@
|
||||
"any_month": "Ямар ч сар",
|
||||
"has_email": "Имэйл хаягтай",
|
||||
"has_phone": "Утастай",
|
||||
"has_photo": "Зурагтай"
|
||||
"has_photo": "Зурагтай",
|
||||
"sort_by": "Эрэмбэлэх",
|
||||
"sort_first_name": "Нэр",
|
||||
"sort_last_name": "Овог"
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "Давхардсан",
|
||||
"today_header": "Өнөөдөр",
|
||||
"tomorrow_header": "Маргааш",
|
||||
"agenda_show_earlier": "Өмнөх үйл явдлуудыг харуулах",
|
||||
"agenda_loading": "Ачаалж байна…",
|
||||
"agenda_range_start": "{date}-с хойшхи үйл явдлууд",
|
||||
"agenda_range_end": "{date} хүртэлх үйл явдлууд",
|
||||
"export_ics": ".ics",
|
||||
"copy_title": "Гарчиг хуулах",
|
||||
"copy_link": "Уулзалтын холбоосыг хуулах",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "Өнгө"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Боломжтой эсэх",
|
||||
"free": "Боломжтой",
|
||||
"busy": "Завгүй",
|
||||
"tentative": "Урьдчилсан",
|
||||
"unknown": "Энэ серверийн хэрэглэгч биш",
|
||||
"checking": "Шалгаж байна…"
|
||||
},
|
||||
"title": "Оролцогчид",
|
||||
"add": "Оролцогч нэмэх",
|
||||
"organizer": "Зохион байгуулагч",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Бүх арга хэмжээ",
|
||||
"cancel": "Цуцлах",
|
||||
"save": "Хадгалах",
|
||||
"delete": "Устгах"
|
||||
"delete": "Устгах",
|
||||
"rsvp_title": "Давтагдах үйл явдалд хариулах",
|
||||
"rsvp_description": "Энэ бол давтагдах үйл явдал. Таны хариу аль үйл явдлуудад хамаарах вэ?",
|
||||
"respond": "Хариулах"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Сануулгууд",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Сарын хуанлийн харагдац дээр үйл явдлын цагийг харуулах. Жижиг дэлгэц дээр энэ нь цэгийн оронд үйл явдлын бүрэн оруулгуудыг харуулдаг.",
|
||||
"show_week_numbers": "Долоо хоногийн дугаарыг харуулах",
|
||||
"show_week_numbers_desc": "Ми-календарт долоо хоногийн тоог харуулах",
|
||||
"calendar_free_scroll": "Чөлөөт гүйлгэлт",
|
||||
"calendar_free_scroll_desc": "Нэг үеийг харуулахын оронд сар, долоо хоног, өдрүүдийг тасралтгүй гүйлгэх",
|
||||
"enable_tasks": "Даалгавруудыг идэвхжүүлэх",
|
||||
"enable_tasks_desc": "Хийх ажлуудыг удирдахын тулд календарьт ажлын харагдах байдлыг харуулах",
|
||||
"show_tasks_on_calendar": "Хуанли дээр даалгавруудыг харуулах",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "1 секундын саатал",
|
||||
"hover_preview_delay_2s": "2 секундын саатал",
|
||||
"hover_preview_off": "Хөгжлийн бэрхшээлтэй",
|
||||
"organizer_identity": "Урилгыг зохион байгуулагч",
|
||||
"organizer_identity_desc": "Хүмүүсийг арга хэмжээнд урихад зохион байгуулагчаар ашиглах хуанлийн хаяг.",
|
||||
"organizer_identity_failed": "Зохион байгуулагчийн хаягийг өөрчилж чадсангүй",
|
||||
"show_birthday_calendar": "Холбоо барих төрсөн өдрийн хуанли",
|
||||
"show_birthday_calendar_desc": "Харилцагчдаас төрсөн өдөр бүхий виртуал хуанли харуулах"
|
||||
},
|
||||
@@ -2973,6 +3043,9 @@
|
||||
"alert_now": "Одооноос эхлэн",
|
||||
"alert_in_minutes": "{count} мин",
|
||||
"invitation_sent": "Урилга илгээсэн",
|
||||
"invitations_denied_title": "Урилга илгээх боломжгүй",
|
||||
"invitations_denied": "Сервер урилга илгээхээс татгалзлаа: {reason}. Урилга илгээхгүйгээр үйл явдлыг хадгалах уу?",
|
||||
"save_without_invitations": "Урилгагүйгээр хадгалах",
|
||||
"rsvp_updated": "Хариулт шинэчлэгдсэн",
|
||||
"rsvp_error": "Хариултыг шинэчилж чадсангүй",
|
||||
"event_duplicated": "Үйл явдал давхардсан",
|
||||
@@ -3002,7 +3075,7 @@
|
||||
"supported_formats": "iCalendar (.ics) файлуудыг дэмждэг",
|
||||
"url_description": "Арга хэмжээг импортлохын тулд гадаад iCalendar (.ics) хангамжийн URL хаягийг оруулна уу.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "CalDAV болон iCalendar (.ics) URL-г дэмждэг",
|
||||
"url_hint": "iCalendar (.ics) URL-г дэмждэг",
|
||||
"fetch": "Авах",
|
||||
"invalid_url": "Хүчинтэй URL оруулна уу",
|
||||
"url_fetch_failed": "URL",
|
||||
@@ -3149,6 +3222,25 @@
|
||||
"overdue": "Хугацаа хэтэрсэн"
|
||||
}
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Хэн нэгэн",
|
||||
"invited": "{name} таныг \"{title}\"-д урилаа",
|
||||
"updated": "{name} \"{title}\"-г шинэчиллээ",
|
||||
"cancelled": "{name} \"{title}\"-г цуцаллаа",
|
||||
"untitled": "Нэргүй арга хэмжээ"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Хэн нэгэн",
|
||||
"shared": "{name} танд {kind} \"{object}\"-г хуваалцлаа",
|
||||
"changed": "{name} таны {kind} \"{object}\"-д хандах эрхийг өөрчиллөө",
|
||||
"revoked": "{name} таны {kind} \"{object}\"-д хандах эрхийг хаслаа",
|
||||
"object": {
|
||||
"folder": "хавтас",
|
||||
"calendar": "хуанли",
|
||||
"address_book": "хаягийн дэвтэр",
|
||||
"files": "файлын хавтас"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "\"{name}\"-г хуваалцаарай",
|
||||
"description": "Энэ сервер дээрх бусад хэрэглэгчид эсвэл бүлгүүдэд хандах эрх олгох. Өөрчлөлтүүд шууд хүчин төгөлдөр болно.",
|
||||
@@ -3187,6 +3279,8 @@
|
||||
"has_attachment": "Хавсралт",
|
||||
"date_after": "Дараа",
|
||||
"date_before": "Өмнө нь",
|
||||
"size_min": "Ихээс (KB)",
|
||||
"size_max": "Багаас (KB)",
|
||||
"starred": "Одтой",
|
||||
"unread": "Уншаагүй",
|
||||
"read": "унших",
|
||||
@@ -3228,6 +3322,10 @@
|
||||
"hint": "Эхлэхийн тулд зүүн талд байгаа дурын имэйл дээр дарж эсвэл доорх аялалд хамрагдаарай."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Баримт засах",
|
||||
"office_loading": "Баримтын засварлагчийг нээж байна…",
|
||||
"office_error": "Баримтын засварлагчийг нээж чадсангүй.",
|
||||
"office_close": "Засварлагчийг хаах",
|
||||
"open_in_new_tab": "Шинэ табаар нээх",
|
||||
"title": "Файл",
|
||||
"search_placeholder": "Файл хайх...",
|
||||
@@ -3240,6 +3338,8 @@
|
||||
"rename": "Нэрээ өөрчлөх",
|
||||
"rename_title": "Нэрээ өөрчлөх",
|
||||
"new_name": "Шинэ нэр",
|
||||
"name_forbidden_chars": "Сервер нэрэнд эдгээр тэмдэгтийг зөвшөөрдөггүй: {chars}",
|
||||
"name_reserved": "Энэ нэр сервер дээр нөөцлөгдсөн.",
|
||||
"delete": "Устгах",
|
||||
"delete_confirm_title": "Нөөцийг устгах",
|
||||
"delete_confirm_message": "Та \"{name}\"-г устгахдаа итгэлтэй байна уу? Үүнийг буцаах боломжгүй.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Арга хэмжээний холбоосыг хуулах",
|
||||
"copied": "Холбоос хуулагдлаа",
|
||||
"copy_failed": "Холбоосыг хуулж чадсангүй"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Зөвшөөрлийг дуусгаж байна…",
|
||||
"completing_message": "Холболт үүсэх хүртэл түр хүлээнэ үү.",
|
||||
"received_title": "Зөвшөөрөл хүлээн авсан",
|
||||
"received_message": "Та холболт эхлүүлсэн таб руугаа буцаж болно.",
|
||||
"failed_title": "Зөвшөөрөл амжилтгүй болсон",
|
||||
"denied": "Зөвшөөрөл олгохыг татгалзсан.",
|
||||
"failed_detail": "Зөвшөөрөл амжилтгүй болсон: {error}",
|
||||
"missing_params": "Зөвшөөрлийн код эсвэл төлөв байхгүй байна.",
|
||||
"delivery_failed": "Зөвшөөрлийн үр дүнг аппын таб руу дамжуулж чадсангүй. Энэ цонхыг хаагаад дахин оролдоно уу."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Бүгдийг хайх",
|
||||
"placeholder": "Шуудан, харилцагч, хуанли, файлаас хайх...",
|
||||
"scope_label": "Хайх хүрээ",
|
||||
"account_label": "Бүртгэл",
|
||||
"scope_all": "Бүгд",
|
||||
"scope_mail": "Мэйл",
|
||||
"scope_contacts": "Харилцах утас",
|
||||
"scope_calendar": "Хуанли",
|
||||
"scope_files": "Файл",
|
||||
"all_accounts": "Бүх бүртгэл",
|
||||
"show_all": "Бүх үр дүнг харуулах",
|
||||
"no_results": "“{query}”-д үр дүн олдсонгүй",
|
||||
"server_hint": "Бүтэн үгс серверээс хайна; үгийн хэсэг ачаалагдсан зүйлстэй таарна",
|
||||
"searching": "Хайж байна...",
|
||||
"account_error": "{account}-аас хайж чадсангүй",
|
||||
"recent_searches": "Сүүлийн хайлтууд",
|
||||
"recurring": "Давтагдах",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Бүх бүртгэлийн шуудан, харилцагч, үйл явдал, файлаас хайгаарай. from:, subject:, has:attachment, after:2026-01-01 гэж туршиж үзээрэй.",
|
||||
"flat_by_date": "Огноогоор жагсаах",
|
||||
"grouped": "Бүлэглэсэн",
|
||||
"load_more": "Цааш ачаалах",
|
||||
"preview_empty": "Урьдчилан харах үр дүнгээ сонгоно уу",
|
||||
"open_full": "Нээх",
|
||||
"view_label": "Харагдац"
|
||||
}
|
||||
}
|
||||
|
||||
+152
-10
@@ -225,6 +225,10 @@
|
||||
"and_others": "og {count} andre",
|
||||
"draft": "Utkast",
|
||||
"starred": "Stjernemerket",
|
||||
"pinned": "Festet",
|
||||
"replied": "Besvart",
|
||||
"forwarded": "Videresendt",
|
||||
"has_attachment": "Har vedlegg",
|
||||
"conversations_count": "{count} av {total} samtaler",
|
||||
"conversations_count_plus": "{count}+ samtaler",
|
||||
"conversations_count_simple": "{count} samtaler",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Videresend",
|
||||
"delete": "Slett",
|
||||
"archive": "Arkiver",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Stjernemerk",
|
||||
"unstar": "Fjern stjerne",
|
||||
"mark_unread": "Merk som ulest",
|
||||
@@ -578,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Ber om lesebekreftelse (klikk for å slå av)",
|
||||
"read_receipt_off": "Be om lesebekreftelse",
|
||||
"dsn_on": "Leveringsvarsel forespurt (klikk for å slå av)",
|
||||
"dsn_off": "Be om leveringsvarsel",
|
||||
"format_plain_text": "Bytt til ren tekst",
|
||||
"format_rich_text": "Bytt til formatert tekst (HTML)",
|
||||
"require_tls_on": "Kryptert levering kreves (klikk for å slå av)",
|
||||
"require_tls_off": "Krev kryptert levering (TLS)",
|
||||
"new_message": "Ny melding",
|
||||
"signature_edit_hint": "Dobbeltklikk for å redigere signaturen",
|
||||
"reply": "Svar",
|
||||
@@ -632,11 +643,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Endre avsender",
|
||||
"toggle_on": "Avbryt endringen",
|
||||
"toggle_tooltip": "Rediger Fra-navnet og -adressen fritt. E-posten sendes fortsatt gjennom identiteten din – bare den synlige Fra-overskriften endres.",
|
||||
"toggle_tooltip": "Rediger Fra-navnet og -adressen fritt. E-posten sendes fortsatt gjennom identiteten din.",
|
||||
"name_label": "Navn i Fra-feltet",
|
||||
"name_placeholder": "Navn",
|
||||
"email_label": "E-postadresse i Fra-feltet",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "Hvis serveren din ikke godtar denne adressen som konvoluttavsender, brukes {identity} i stedet, og mottakerne kan se den i Return-Path-overskriften."
|
||||
},
|
||||
"use_template": "Mal",
|
||||
"save_as_template": "Lagre som mal",
|
||||
@@ -649,6 +661,8 @@
|
||||
"upload_progress": "Laster opp {uploaded} / {total}",
|
||||
"upload_cancel": "Avbryt opplasting",
|
||||
"upload_failed": "Kunne ikke laste opp {filename}",
|
||||
"attachment_too_large": "«{name}» er større enn serveren tillater ({max} per fil)",
|
||||
"attachments_total_too_large": "Vedleggene ville overskride serverens grense på {max} per melding",
|
||||
"drop_files": "Slipp filer for å legge ved",
|
||||
"show_less": "Vis færre",
|
||||
"send_failed": "Kunne ikke sende e-post",
|
||||
@@ -724,7 +738,11 @@
|
||||
"delete_column": "Slett kolonne",
|
||||
"toggle_header_row": "Slå overskriftsraden av eller på",
|
||||
"delete_table": "Slett tabell",
|
||||
"pick_size": "Velg størrelse"
|
||||
"pick_size": "Velg størrelse",
|
||||
"font_size": "Skriftstørrelse",
|
||||
"font_size_default": "Standard",
|
||||
"background_color": "Bakgrunnsfarge",
|
||||
"remove_background_color": "Fjern bakgrunnsfarge"
|
||||
},
|
||||
"send_filing_warning": "Meldingen ble sendt, men oppryddingen etterpå mislyktes. Et gammelt utkast kan bli liggende."
|
||||
},
|
||||
@@ -763,6 +781,10 @@
|
||||
"email_sent": "E-posten er sendt",
|
||||
"email_deleted": "E-posten er slettet",
|
||||
"email_archived": "E-posten er arkivert",
|
||||
"emails_deleted": "{count, plural, one {# e-post slettet} other {# e-poster slettet}}",
|
||||
"emails_archived": "{count, plural, one {# e-post arkivert} other {# e-poster arkivert}}",
|
||||
"error_archiving": "Kunne ikke arkivere e-posten",
|
||||
"error_updating": "Kunne ikke oppdatere e-posten",
|
||||
"email_starred": "E-posten er stjernemerket",
|
||||
"email_unstarred": "E-posten er ikke lenger stjernemerket",
|
||||
"email_marked_read": "E-posten er merket som lest",
|
||||
@@ -1142,6 +1164,7 @@
|
||||
"status_active": "Aktiv på denne enheten",
|
||||
"status_inactive": "Ikke aktivert på denne enheten",
|
||||
"status_unsupported": "Denne nettleseren støtter ikke Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"status_busy": "Behandler...",
|
||||
"enable": "Aktiver",
|
||||
"reenable": "Registrer på nytt",
|
||||
@@ -1179,7 +1202,9 @@
|
||||
"enabled": "E-postvarsler",
|
||||
"enabled_desc": "Vis varsler når nye e-poster kommer",
|
||||
"sound": "Varslingslyd",
|
||||
"sound_desc": "Spill av et lydvarsel når nye e-poster kommer"
|
||||
"sound_desc": "Spill av et lydvarsel når nye e-poster kommer",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Kalendervarsler",
|
||||
@@ -1239,6 +1264,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Tøm søket ved bytte av mappe",
|
||||
"description": "Å klikke på en mappe (Innboks, Sendt …) tømmer det aktive søket og viser mappen. Når det er av, beholdes søket og kjøres på nytt i mappen du bytter til."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Be om lesebekreftelser som standard",
|
||||
"description": "Be automatisk om lesebekreftelse når du skriver en ny melding."
|
||||
@@ -1322,6 +1351,12 @@
|
||||
"label": "Svar fra mottatt adresse",
|
||||
"description": "Når du svarer, bruker Bulwark adressen meldingen opprinnelig ble sendt til. Programmet ser først etter en samsvarende identitet. For meldinger til et oppsamlingsdomene brukes aliaset i Fra-feltet, mens sendingen går gjennom hovedidentiteten din."
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Samsvar for mottaksadresse",
|
||||
"description": "Hvilke mottaksadresser som regnes som dine. «Kun nøyaktig adresse» velger bare en av dine oppsatte identiteter; «samme domene» behandler i tillegg enhver annen adresse på identitetsdomenene dine som et catch-all-alias og skriver om Fra-headeren til den. Velg nøyaktig adresse hvis disse adressene er distribusjonslister.",
|
||||
"exact": "Kun nøyaktig adresse",
|
||||
"domain": "Enhver adresse på mine domener"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Signaturposisjon",
|
||||
"description": "Velg hvor signaturen skal settes inn i svar og videresendinger. Over den siterte teksten fungerer den som en naturlig avslutning på svaret. Under den siterte teksten holdes den opprinnelige meldingen samlet.",
|
||||
@@ -1751,7 +1786,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "Sluttdatoen må være etter startdatoen",
|
||||
"start_in_past": "Startdatoen er i fortiden",
|
||||
"empty_body": "Meldingsteksten er tom – mottakerne får et tomt svar"
|
||||
"empty_body": "Meldingsteksten er tom – mottakerne får et tomt svar",
|
||||
"subject_too_long": "Emnet er for langt for serveren (maks. {max} byte).",
|
||||
"body_too_long": "Meldingen er for lang for serveren (maks. {max} byte inkludert formatering)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1768,6 +1805,8 @@
|
||||
"role_trash": "Papirkurv",
|
||||
"role_junk": "Søppelpost",
|
||||
"role_archive": "Arkiv",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Ingen",
|
||||
"create_folder": "Opprett mappe",
|
||||
"create_subfolder": "Opprett undermappe",
|
||||
@@ -1877,6 +1916,8 @@
|
||||
"title": "Kontakter",
|
||||
"description": "Importer og eksporter kontaktene dine",
|
||||
"group_by_letter_label": "Grupper etter første bokstav",
|
||||
"sort_by_last_name_label": "Sorter etter etternavn",
|
||||
"sort_by_last_name_description": "Sorter kontaktlisten etter etternavn slik at familiemedlemmer står samlet",
|
||||
"group_by_letter_description": "Vis alfabetiske overskrifter i kontaktlisten",
|
||||
"import_label": "Importer kontakter",
|
||||
"import_description": "Importer kontakter fra en vCard-fil (.vcf).",
|
||||
@@ -1968,6 +2009,7 @@
|
||||
"actions": "Handlinger",
|
||||
"add_action": "Legg til handling",
|
||||
"stop_processing": "Ikke behandle flere regler",
|
||||
"include_spam": "Flytt også meldinger som er merket som søppelpost",
|
||||
"attachment_type_placeholder": "f.eks. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Verdi (skill flere verdier med komma)",
|
||||
"condition_fields": {
|
||||
@@ -2009,6 +2051,8 @@
|
||||
"copy_to_folder": "Velg mappe",
|
||||
"forward_to": "Videresend til e-postadresse",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "Behold en kopi",
|
||||
"forward_limit": "Grense for videresendinger per melding på denne serveren: {count}. Flere videresendinger hoppes over.",
|
||||
"reject_message": "Avvisningsmelding",
|
||||
"reject_placeholder": "Meldingen din er avvist",
|
||||
"label_name": "Etikettnavn",
|
||||
@@ -2205,6 +2249,7 @@
|
||||
"new_subfolder": "Ny undermappe...",
|
||||
"new_folder": "Ny mappe...",
|
||||
"rename": "Gi nytt navn...",
|
||||
"share": "Del...",
|
||||
"import_email": "Importer .eml eller .zip...",
|
||||
"empty_folder": "Tøm mappe",
|
||||
"empty_folder_generic": "Tøm mappe",
|
||||
@@ -2234,7 +2279,10 @@
|
||||
"toast_error_rename": "Kunne ikke gi nytt navn til mappen",
|
||||
"toast_error_delete": "Kunne ikke slette mappen",
|
||||
"toast_error_delete_has_children": "Mappen har undermapper. Fjern dem først.",
|
||||
"toast_error_delete_has_email": "Mappen er ikke tom. Tøm den først."
|
||||
"toast_error_delete_has_email": "Mappen er ikke tom. Tøm den først.",
|
||||
"delete_with_contents_title": "Mappen er ikke tom",
|
||||
"delete_with_contents_message": "Mappen «{name}» inneholder fortsatt {count, plural, one {1 melding} other {# meldinger}}. Slette mappen og alle meldingene i den? Dette kan ikke angres.",
|
||||
"delete_with_contents_confirm": "Slett mappe og meldinger"
|
||||
},
|
||||
"shortcuts": {
|
||||
"title": "Tastatursnarveier",
|
||||
@@ -2268,7 +2316,8 @@
|
||||
"search": "Aktiver søkefeltet",
|
||||
"help": "Vis tastatursnarveier",
|
||||
"refresh": "Oppdater e-poster",
|
||||
"select_all": "Velg alle"
|
||||
"select_all": "Velg alle",
|
||||
"global_search": "Søk i alt"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Utvid eller fold sammen tråden"
|
||||
@@ -2720,7 +2769,10 @@
|
||||
"any_month": "Alle måneder",
|
||||
"has_email": "Har e-postadresse",
|
||||
"has_phone": "Har telefonnummer",
|
||||
"has_photo": "Har bilde"
|
||||
"has_photo": "Har bilde",
|
||||
"sort_by": "Sorter etter",
|
||||
"sort_first_name": "Fornavn",
|
||||
"sort_last_name": "Etternavn"
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
@@ -2756,6 +2808,10 @@
|
||||
"duplicate": "Dupliser",
|
||||
"today_header": "I dag",
|
||||
"tomorrow_header": "I morgen",
|
||||
"agenda_show_earlier": "Vis tidligere hendelser",
|
||||
"agenda_loading": "Laster…",
|
||||
"agenda_range_start": "Hendelser fra {date}",
|
||||
"agenda_range_end": "Hendelser til {date}",
|
||||
"export_ics": "Eksporter som .ics",
|
||||
"copy_title": "Kopier tittel",
|
||||
"copy_link": "Kopier møtelenken",
|
||||
@@ -2791,6 +2847,14 @@
|
||||
"color": "Farge"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Tilgjengelighet",
|
||||
"free": "Ledig",
|
||||
"busy": "Opptatt",
|
||||
"tentative": "Foreløpig",
|
||||
"unknown": "Ikke en bruker på denne serveren",
|
||||
"checking": "Sjekker…"
|
||||
},
|
||||
"title": "Deltakere",
|
||||
"add": "Legg til deltaker",
|
||||
"organizer": "Arrangør",
|
||||
@@ -2865,7 +2929,10 @@
|
||||
"all_events": "Alle hendelser",
|
||||
"cancel": "Avbryt",
|
||||
"save": "Lagre",
|
||||
"delete": "Slett"
|
||||
"delete": "Slett",
|
||||
"rsvp_title": "Svar på gjentakende hendelse",
|
||||
"rsvp_description": "Dette er en gjentakende hendelse. Hvilke hendelser gjelder svaret ditt for?",
|
||||
"respond": "Svar"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Påminnelser",
|
||||
@@ -2903,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Vis klokkeslettet for hendelser i månedsvisningen. På små skjermer vises hele hendelsesoppføringen i stedet for en prikk.",
|
||||
"show_week_numbers": "Vis ukenummer",
|
||||
"show_week_numbers_desc": "Vis ukenummer i minikalenderen",
|
||||
"calendar_free_scroll": "Fri rulling",
|
||||
"calendar_free_scroll_desc": "Rull fortløpende gjennom måneder, uker og dager i stedet for én periode om gangen",
|
||||
"enable_tasks": "Aktiver oppgaver",
|
||||
"enable_tasks_desc": "Vis en oppgavevisning i kalenderen for å administrere gjøremål",
|
||||
"show_tasks_on_calendar": "Vis oppgaver i kalenderen",
|
||||
@@ -2914,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "Forsinkelse på 1 sekund",
|
||||
"hover_preview_delay_2s": "Forsinkelse på 2 sekunder",
|
||||
"hover_preview_off": "Deaktivert",
|
||||
"organizer_identity": "Organiser invitasjoner som",
|
||||
"organizer_identity_desc": "Kalenderadressen som brukes som arrangør når du inviterer folk til hendelser.",
|
||||
"organizer_identity_failed": "Kunne ikke endre arrangøradressen",
|
||||
"show_birthday_calendar": "Fødselsdager fra kontakter",
|
||||
"show_birthday_calendar_desc": "Vis en virtuell kalender med bursdager fra kontaktene dine"
|
||||
},
|
||||
@@ -2971,6 +3043,9 @@
|
||||
"alert_now": "Starter nå",
|
||||
"alert_in_minutes": "Om {count} min",
|
||||
"invitation_sent": "Invitasjonene er sendt",
|
||||
"invitations_denied_title": "Invitasjonene kan ikke sendes",
|
||||
"invitations_denied": "Serveren nektet å sende invitasjonene: {reason}. Lagre hendelsen uten å sende dem?",
|
||||
"save_without_invitations": "Lagre uten invitasjoner",
|
||||
"rsvp_updated": "Svaret er oppdatert",
|
||||
"rsvp_error": "Kunne ikke oppdatere svaret",
|
||||
"event_duplicated": "Hendelsen er duplisert",
|
||||
@@ -3000,7 +3075,7 @@
|
||||
"supported_formats": "Støtter iCalendar (.ics)-filer",
|
||||
"url_description": "Skriv inn URL-en til en ekstern iCalendar-strøm (.ics) for å importere hendelser.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "Støtter CalDAV- og iCalendar-URL-er (.ics)",
|
||||
"url_hint": "Støtter iCalendar-URL-er (.ics)",
|
||||
"fetch": "Hent",
|
||||
"invalid_url": "Skriv inn en gyldig URL",
|
||||
"url_fetch_failed": "Kunne ikke hente kalenderen fra nettadressen",
|
||||
@@ -3147,6 +3222,25 @@
|
||||
"overdue": "Forfalt"
|
||||
}
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Noen",
|
||||
"invited": "{name} har invitert deg til «{title}»",
|
||||
"updated": "{name} har oppdatert «{title}»",
|
||||
"cancelled": "{name} har avlyst «{title}»",
|
||||
"untitled": "Hendelse uten tittel"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Noen",
|
||||
"shared": "{name} har delt {kind} «{object}» med deg",
|
||||
"changed": "{name} har endret tilgangen din til {kind} «{object}»",
|
||||
"revoked": "{name} har fjernet tilgangen din til {kind} «{object}»",
|
||||
"object": {
|
||||
"folder": "mappen",
|
||||
"calendar": "kalenderen",
|
||||
"address_book": "adresseboken",
|
||||
"files": "filmappen"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Del «{name}»",
|
||||
"description": "Gi andre brukere eller grupper på denne serveren tilgang. Endringer trer i kraft umiddelbart.",
|
||||
@@ -3185,6 +3279,8 @@
|
||||
"has_attachment": "Vedlegg",
|
||||
"date_after": "Etter",
|
||||
"date_before": "Før",
|
||||
"size_min": "Større enn (KB)",
|
||||
"size_max": "Mindre enn (KB)",
|
||||
"starred": "Stjernemerket",
|
||||
"unread": "Ulest",
|
||||
"read": "Lest",
|
||||
@@ -3226,6 +3322,10 @@
|
||||
"hint": "Klikk på en av e-postene til venstre for å komme i gang, eller start omvisningen nedenfor."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Rediger dokument",
|
||||
"office_loading": "Åpner dokumentredigering…",
|
||||
"office_error": "Dokumentredigeringen kunne ikke åpnes.",
|
||||
"office_close": "Lukk redigering",
|
||||
"open_in_new_tab": "Åpne i ny fane",
|
||||
"title": "Filer",
|
||||
"search_placeholder": "Søk etter filer...",
|
||||
@@ -3238,6 +3338,8 @@
|
||||
"rename": "Gi nytt navn",
|
||||
"rename_title": "Gi nytt navn",
|
||||
"new_name": "Nytt navn",
|
||||
"name_forbidden_chars": "Serveren tillater ikke disse tegnene i navn: {chars}",
|
||||
"name_reserved": "Dette navnet er reservert på serveren.",
|
||||
"delete": "Slett",
|
||||
"delete_confirm_title": "Slett fil eller mappe",
|
||||
"delete_confirm_message": "Er du sikker på at du vil slette «{name}»? Dette kan ikke angres.",
|
||||
@@ -3499,5 +3601,45 @@
|
||||
"copy_event": "Kopier lenken til hendelsen",
|
||||
"copied": "Lenken er kopiert",
|
||||
"copy_failed": "Kunne ikke kopiere lenken"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Fullfører autorisering…",
|
||||
"completing_message": "Vennligst vent mens tilkoblingen opprettes.",
|
||||
"received_title": "Autorisering mottatt",
|
||||
"received_message": "Du kan gå tilbake til fanen der du startet tilkoblingen.",
|
||||
"failed_title": "Autorisering mislyktes",
|
||||
"denied": "Autorisering ble avslått.",
|
||||
"failed_detail": "Autorisering mislyktes: {error}",
|
||||
"missing_params": "Mangler autorisasjonskode eller tilstand.",
|
||||
"delivery_failed": "Autoriseringsresultatet kunne ikke leveres til app-fanen. Lukk dette vinduet og prøv igjen."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Søk i alt",
|
||||
"placeholder": "Søk i e-post, kontakter, kalender, filer...",
|
||||
"scope_label": "Søk i",
|
||||
"account_label": "Konto",
|
||||
"scope_all": "Alt",
|
||||
"scope_mail": "E-post",
|
||||
"scope_contacts": "Kontakter",
|
||||
"scope_calendar": "Kalender",
|
||||
"scope_files": "Filer",
|
||||
"all_accounts": "Alle kontoer",
|
||||
"show_all": "Vis alle resultater",
|
||||
"no_results": "Ingen resultater for “{query}”",
|
||||
"server_hint": "Hele ord søker på serveren; deler av ord treffer allerede innlastede elementer",
|
||||
"searching": "Søker...",
|
||||
"account_error": "Kunne ikke søke i {account}",
|
||||
"recent_searches": "Nylige søk",
|
||||
"recurring": "Gjentakende",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Søk i e-post, kontakter, hendelser og filer på alle kontoer. Prøv from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Flatt etter dato",
|
||||
"grouped": "Gruppert",
|
||||
"load_more": "Last inn flere",
|
||||
"preview_empty": "Velg et resultat for å forhåndsvise det",
|
||||
"open_full": "Åpne",
|
||||
"view_label": "Visning"
|
||||
}
|
||||
}
|
||||
|
||||
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "en {count} anderen",
|
||||
"draft": "Concept",
|
||||
"starred": "Met ster",
|
||||
"pinned": "Vastgezet",
|
||||
"replied": "Beantwoord",
|
||||
"forwarded": "Doorgestuurd",
|
||||
"has_attachment": "Met bijlage",
|
||||
"conversations_count": "{count} van {total} gesprekken",
|
||||
"conversations_count_plus": "{count}+ gesprekken",
|
||||
"conversations_count_simple": "{count} gesprekken",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Doorsturen",
|
||||
"delete": "Verwijderen",
|
||||
"archive": "Archiveren",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Ster toevoegen",
|
||||
"unstar": "Ster verwijderen",
|
||||
"mark_unread": "Markeren als ongelezen",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Verplaatsen",
|
||||
"print": "Afdrukken",
|
||||
"view_source": "Bron bekijken",
|
||||
|
||||
"fullscreen": "Volledig scherm",
|
||||
|
||||
"exit_fullscreen": "Volledig scherm sluiten",
|
||||
"export_email": "Exporteren als .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Doorsturen als bijlage",
|
||||
"import_email": ".eml of .zip importeren",
|
||||
"keyboard_shortcuts": "Sneltoetsen (?)",
|
||||
"email_source": "E-mailbron",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Leesbevestiging aangevraagd (klik om uit te schakelen)",
|
||||
"read_receipt_off": "Leesbevestiging aanvragen",
|
||||
"dsn_on": "Bezorgmelding aangevraagd (klik om uit te schakelen)",
|
||||
"dsn_off": "Bezorgmelding aanvragen",
|
||||
"format_plain_text": "Overschakelen naar platte tekst",
|
||||
"format_rich_text": "Overschakelen naar opgemaakte tekst (HTML)",
|
||||
"require_tls_on": "Versleutelde bezorging vereist (klik om uit te schakelen)",
|
||||
"require_tls_off": "Versleutelde bezorging vereisen (TLS)",
|
||||
"new_message": "Nieuw bericht",
|
||||
"signature_edit_hint": "Dubbelklik om de handtekening te bewerken",
|
||||
"reply": "Beantwoorden",
|
||||
@@ -631,11 +640,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Overschrijven",
|
||||
"toggle_on": "Overschrijven annuleren",
|
||||
"toggle_tooltip": "Bewerk de naam en het adres van de afzender vrij. E-mail wordt nog steeds via je identiteit verzonden - alleen de zichtbare Van-koptekst verandert.",
|
||||
"toggle_tooltip": "Bewerk de naam en het adres van de afzender vrij. E-mail wordt nog steeds via je identiteit verzonden.",
|
||||
"name_label": "Afzendernaam",
|
||||
"name_placeholder": "Naam",
|
||||
"email_label": "E-mailadres afzender",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "Als je server dit adres niet accepteert als envelopafzender, wordt daar {identity} gebruikt, en ontvangers kunnen dat zien in de Return-Path-header."
|
||||
},
|
||||
"use_template": "Sjabloon",
|
||||
"save_as_template": "Opslaan als sjabloon",
|
||||
@@ -648,6 +658,8 @@
|
||||
"upload_progress": "Uploaden {uploaded} / {total}",
|
||||
"upload_cancel": "Upload annuleren",
|
||||
"upload_failed": "Uploaden van {filename} mislukt",
|
||||
"attachment_too_large": "\"{name}\" is groter dan de server toestaat ({max} per bestand)",
|
||||
"attachments_total_too_large": "De bijlagen zouden de serverlimiet van {max} per bericht overschrijden",
|
||||
"send_failed": "E-mail verzenden mislukt",
|
||||
"smime_sign_on": "S/MIME-ondertekening ingeschakeld",
|
||||
"smime_sign_off": "S/MIME-ondertekening uitgeschakeld",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Kolom verwijderen",
|
||||
"toggle_header_row": "Koprij aan/uit",
|
||||
"delete_table": "Tabel verwijderen",
|
||||
"pick_size": "Grootte kiezen"
|
||||
"pick_size": "Grootte kiezen",
|
||||
"font_size": "Lettergrootte",
|
||||
"font_size_default": "Standaard",
|
||||
"background_color": "Achtergrondkleur",
|
||||
"remove_background_color": "Achtergrondkleur verwijderen"
|
||||
},
|
||||
"send_filing_warning": "Verzonden - maar het opruimen daarna is mislukt, mogelijk blijft een oud concept staan."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "E-mail succesvol verzonden",
|
||||
"email_deleted": "E-mail verwijderd",
|
||||
"email_archived": "E-mail gearchiveerd",
|
||||
"emails_deleted": "{count, plural, one {# e-mail verwijderd} other {# e-mails verwijderd}}",
|
||||
"emails_archived": "{count, plural, one {# e-mail gearchiveerd} other {# e-mails gearchiveerd}}",
|
||||
"error_archiving": "E-mail kon niet worden gearchiveerd",
|
||||
"error_updating": "E-mail kon niet worden bijgewerkt",
|
||||
"email_starred": "Ster toegevoegd aan e-mail",
|
||||
"email_unstarred": "Ster verwijderd van e-mail",
|
||||
"email_marked_read": "E-mail gemarkeerd als gelezen",
|
||||
@@ -1143,7 +1163,9 @@
|
||||
"enabled": "E-mailmeldingen",
|
||||
"enabled_desc": "Meldingen tonen wanneer nieuwe e-mails binnenkomen",
|
||||
"sound": "Meldingsgeluid",
|
||||
"sound_desc": "Een geluid afspelen wanneer nieuwe e-mails binnenkomen"
|
||||
"sound_desc": "Een geluid afspelen wanneer nieuwe e-mails binnenkomen",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Agendameldingen",
|
||||
@@ -1172,6 +1194,7 @@
|
||||
"status_busy": "Bezig…",
|
||||
"status_inactive": "Niet ingeschakeld op dit apparaat",
|
||||
"status_unsupported": "Deze browser ondersteunt geen Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"title": "Achtergrondmeldingen",
|
||||
"devices_title": "Geregistreerde apparaten",
|
||||
"devices_desc": "Alle apparaten die achtergrondmeldingen voor dit account ontvangen. Trek je er een in, dan stopt de ontvangst meteen.",
|
||||
@@ -1238,6 +1261,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Zoekopdracht wissen bij wisselen van map",
|
||||
"description": "Op een map klikken (Postvak IN, Verzonden…) wist de actieve zoekopdracht en toont die map. Uitgeschakeld blijft de zoekopdracht staan en wordt opnieuw uitgevoerd in de gekozen map."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Standaard om leesbevestiging vragen",
|
||||
"description": "De aanvraag voor een leesbevestiging vooraf inschakelen bij het opstellen van een nieuw bericht."
|
||||
@@ -1316,6 +1343,12 @@
|
||||
"label": "Antwoordadres automatisch selecteren",
|
||||
"description": "Schakel bij het beantwoorden automatisch het Van-adres om naar de identiteit die het oorspronkelijke bericht ontving"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Overeenkomst van ontvangstadres",
|
||||
"description": "Welke ontvangstadressen als de uwe gelden. „Alleen exact adres“ kiest uitsluitend een van uw ingestelde identiteiten; „zelfde domein“ behandelt ook elk ander adres op uw identiteitsdomeinen als catch-all-alias en herschrijft de Van-header ernaar. Kies exact adres als die adressen distributielijsten zijn.",
|
||||
"exact": "Alleen exact adres",
|
||||
"domain": "Elk adres op mijn domeinen"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Positie van handtekening",
|
||||
"description": "Waar je handtekening in antwoorden en doorgestuurde berichten moet worden ingevoegd. Boven de geciteerde tekst leest natuurlijk als afsluiting van het antwoord; eronder houdt het originele bericht aaneengesloten.",
|
||||
@@ -1746,7 +1779,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "De einddatum moet na de startdatum liggen",
|
||||
"start_in_past": "De startdatum ligt in het verleden",
|
||||
"empty_body": "De berichttekst is leeg - ontvangers krijgen een leeg antwoord"
|
||||
"empty_body": "De berichttekst is leeg - ontvangers krijgen een leeg antwoord",
|
||||
"subject_too_long": "Het onderwerp is te lang voor de server (maximaal {max} bytes).",
|
||||
"body_too_long": "Het bericht is te lang voor de server (maximaal {max} bytes, inclusief opmaak)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1763,6 +1798,8 @@
|
||||
"role_trash": "Prullenbak",
|
||||
"role_junk": "Spam / Ongewenst",
|
||||
"role_archive": "Archief",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Geen",
|
||||
"create_folder": "Map maken",
|
||||
"create_subfolder": "Submap maken",
|
||||
@@ -1882,7 +1919,9 @@
|
||||
"categories_description": "Contactcategorieën hernoemen",
|
||||
"no_categories": "Geen categorieën gevonden",
|
||||
"group_by_letter_description": "Toon alfabetische sectiekoppen in de contactenlijst",
|
||||
"group_by_letter_label": "Groeperen op eerste letter"
|
||||
"group_by_letter_label": "Groeperen op eerste letter",
|
||||
"sort_by_last_name_label": "Sorteren op achternaam",
|
||||
"sort_by_last_name_description": "Sorteer de contactenlijst op achternaam zodat gezinsleden bij elkaar staan"
|
||||
},
|
||||
"downloads": {
|
||||
"title": "Downloads",
|
||||
@@ -1963,6 +2002,7 @@
|
||||
"actions": "Acties",
|
||||
"add_action": "Actie toevoegen",
|
||||
"stop_processing": "Verwerking van volgende regels stoppen",
|
||||
"include_spam": "Ook berichten verplaatsen die als spam zijn gemarkeerd",
|
||||
"attachment_type_placeholder": "bijv. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Waarde (meerdere met komma's gescheiden)",
|
||||
"condition_fields": {
|
||||
@@ -2004,6 +2044,8 @@
|
||||
"copy_to_folder": "Map selecteren",
|
||||
"forward_to": "Doorsturen naar e-mailadres",
|
||||
"forward_placeholder": "email@voorbeeld.nl",
|
||||
"forward_keep_copy": "Kopie bewaren",
|
||||
"forward_limit": "Maximaal aantal doorsturingen per bericht op deze server: {count}. Extra doorsturingen worden overgeslagen.",
|
||||
"reject_message": "Afwijzingsbericht",
|
||||
"reject_placeholder": "Uw e-mail is afgewezen",
|
||||
"label_name": "Labelnaam",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Nieuwe submap...",
|
||||
"new_folder": "Nieuwe map...",
|
||||
"rename": "Hernoemen...",
|
||||
"share": "Delen...",
|
||||
"import_email": ".eml of .zip importeren...",
|
||||
"empty_folder": "Map leegmaken",
|
||||
"empty_folder_generic": "Map leegmaken",
|
||||
@@ -2234,6 +2277,9 @@
|
||||
"toast_error_delete": "Map verwijderen mislukt",
|
||||
"toast_error_delete_has_children": "Map bevat submappen. Verwijder deze eerst.",
|
||||
"toast_error_delete_has_email": "Map is niet leeg. Maak deze eerst leeg.",
|
||||
"delete_with_contents_title": "Map is niet leeg",
|
||||
"delete_with_contents_message": "De map \"{name}\" bevat nog {count, plural, one {1 bericht} other {# berichten}}. De map en al haar berichten verwijderen? Dit kan niet ongedaan worden gemaakt.",
|
||||
"delete_with_contents_confirm": "Map en berichten verwijderen",
|
||||
"placeholder_folder_name": "Mapnaam",
|
||||
"create": "Aanmaken",
|
||||
"rename_confirm": "Hernoemen"
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Focus op zoeken",
|
||||
"help": "Sneltoetsen tonen",
|
||||
"refresh": "E-mails vernieuwen",
|
||||
"select_all": "Alles selecteren"
|
||||
"select_all": "Alles selecteren",
|
||||
"global_search": "Alles doorzoeken"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Gesprek uitklappen/inklappen"
|
||||
@@ -2721,7 +2768,10 @@
|
||||
"any_month": "Elke maand",
|
||||
"has_email": "Met e-mail",
|
||||
"has_phone": "Met telefoon",
|
||||
"has_photo": "Met foto"
|
||||
"has_photo": "Met foto",
|
||||
"sort_by": "Sorteren op",
|
||||
"sort_first_name": "Voornaam",
|
||||
"sort_last_name": "Achternaam"
|
||||
},
|
||||
"open_categories": "Categorieën openen"
|
||||
},
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "Dupliceren",
|
||||
"today_header": "Vandaag",
|
||||
"tomorrow_header": "Morgen",
|
||||
"agenda_show_earlier": "Eerdere afspraken tonen",
|
||||
"agenda_loading": "Laden…",
|
||||
"agenda_range_start": "Afspraken vanaf {date}",
|
||||
"agenda_range_end": "Afspraken tot {date}",
|
||||
"export_ics": "Exporteren als .ics",
|
||||
"copy_title": "Titel kopiëren",
|
||||
"copy_link": "Vergaderlink kopiëren",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "Kleur"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Beschikbaarheid",
|
||||
"free": "Beschikbaar",
|
||||
"busy": "Bezet",
|
||||
"tentative": "Voorlopig",
|
||||
"unknown": "Geen gebruiker op deze server",
|
||||
"checking": "Controleren…"
|
||||
},
|
||||
"title": "Deelnemers",
|
||||
"add": "Deelnemer toevoegen",
|
||||
"organizer": "Organisator",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Alle evenementen",
|
||||
"cancel": "Annuleren",
|
||||
"save": "Opslaan",
|
||||
"delete": "Verwijderen"
|
||||
"delete": "Verwijderen",
|
||||
"rsvp_title": "Reageren op terugkerend evenement",
|
||||
"rsvp_description": "Dit is een terugkerend evenement. Voor welke evenementen geldt uw reactie?",
|
||||
"respond": "Reageren"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Herinnering",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Evenementtijden weergeven in de maandkalenderweergave",
|
||||
"show_week_numbers": "Weeknummers weergeven",
|
||||
"show_week_numbers_desc": "Weeknummers weergeven in de minikalender",
|
||||
"calendar_free_scroll": "Vrij scrollen",
|
||||
"calendar_free_scroll_desc": "Doorlopend door maanden, weken en dagen scrollen in plaats van één periode tegelijk",
|
||||
"enable_tasks": "Taken inschakelen",
|
||||
"enable_tasks_desc": "Een takenweergave in de agenda tonen om to-do's te beheren",
|
||||
"show_tasks_on_calendar": "Taken in agenda tonen",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "1 seconde vertraging",
|
||||
"hover_preview_delay_2s": "2 seconden vertraging",
|
||||
"hover_preview_off": "Uitgeschakeld",
|
||||
"organizer_identity": "Uitnodigingen organiseren als",
|
||||
"organizer_identity_desc": "Het agenda-adres dat als organisator wordt gebruikt wanneer je mensen uitnodigt voor afspraken.",
|
||||
"organizer_identity_failed": "Kon het organisatoradres niet wijzigen",
|
||||
"show_birthday_calendar": "Verjaardagskalender",
|
||||
"show_birthday_calendar_desc": "Toon een virtuele kalender met verjaardagen van je contacten"
|
||||
},
|
||||
@@ -2947,6 +3017,9 @@
|
||||
"alert_now": "Begint nu",
|
||||
"alert_in_minutes": "Over {count} min",
|
||||
"invitation_sent": "Uitnodigingen verzonden",
|
||||
"invitations_denied_title": "Uitnodigingen kunnen niet worden verzonden",
|
||||
"invitations_denied": "De server weigerde de uitnodigingen te verzenden: {reason}. Het evenement opslaan zonder ze te verzenden?",
|
||||
"save_without_invitations": "Opslaan zonder uitnodigingen",
|
||||
"rsvp_updated": "Reactie bijgewerkt",
|
||||
"rsvp_error": "Reactie kon niet worden bijgewerkt",
|
||||
"event_duplicated": "Evenement gedupliceerd",
|
||||
@@ -2975,7 +3048,7 @@
|
||||
"supported_formats": "iCalendar (.ics) bestanden worden ondersteund",
|
||||
"url_description": "Voer de URL in van een externe iCalendar (.ics) feed om evenementen te importeren.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "Ondersteunt CalDAV en iCalendar (.ics) URLs",
|
||||
"url_hint": "Ondersteunt iCalendar (.ics) URLs",
|
||||
"fetch": "Ophalen",
|
||||
"invalid_url": "Voer een geldige URL in",
|
||||
"url_fetch_failed": "Kan agenda niet ophalen van URL",
|
||||
@@ -3164,6 +3237,8 @@
|
||||
"has_attachment": "Bijlagen",
|
||||
"date_after": "Na",
|
||||
"date_before": "Voor",
|
||||
"size_min": "Groter dan (KB)",
|
||||
"size_max": "Kleiner dan (KB)",
|
||||
"starred": "Met ster",
|
||||
"unread": "Ongelezen",
|
||||
"read": "Gelezen",
|
||||
@@ -3205,6 +3280,10 @@
|
||||
"hint": "Klik links op een e-mail om te beginnen, of start de tour."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Document bewerken",
|
||||
"office_loading": "Documenteditor wordt geopend…",
|
||||
"office_error": "De documenteditor kon niet worden geopend.",
|
||||
"office_close": "Editor sluiten",
|
||||
"open_in_new_tab": "Openen in nieuw tabblad",
|
||||
"title": "Bestanden",
|
||||
"search_placeholder": "Bestanden zoeken...",
|
||||
@@ -3217,6 +3296,8 @@
|
||||
"rename": "Hernoemen",
|
||||
"rename_title": "Hernoemen",
|
||||
"new_name": "Nieuwe naam",
|
||||
"name_forbidden_chars": "De server staat deze tekens niet toe in namen: {chars}",
|
||||
"name_reserved": "Deze naam is gereserveerd op de server.",
|
||||
"delete": "Verwijderen",
|
||||
"delete_confirm_title": "Bron verwijderen",
|
||||
"delete_confirm_message": "Weet u zeker dat u \"{name}\" wilt verwijderen? Dit kan niet ongedaan worden gemaakt.",
|
||||
@@ -3451,6 +3532,25 @@
|
||||
"unified_mailbox": {
|
||||
"search_unavailable": "Zoeken is niet beschikbaar in de gecombineerde weergave"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Iemand",
|
||||
"invited": "{name} heeft je uitgenodigd voor \"{title}\"",
|
||||
"updated": "{name} heeft \"{title}\" bijgewerkt",
|
||||
"cancelled": "{name} heeft \"{title}\" geannuleerd",
|
||||
"untitled": "Afspraak zonder titel"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Iemand",
|
||||
"shared": "{name} heeft {kind} \"{object}\" met je gedeeld",
|
||||
"changed": "{name} heeft je toegang tot {kind} \"{object}\" gewijzigd",
|
||||
"revoked": "{name} heeft je toegang tot {kind} \"{object}\" ingetrokken",
|
||||
"object": {
|
||||
"folder": "de map",
|
||||
"calendar": "de agenda",
|
||||
"address_book": "het adresboek",
|
||||
"files": "de bestandsmap"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "\"{name}\" delen",
|
||||
"description": "Geef andere gebruikers of groepen op deze server toegang. Wijzigingen zijn direct van kracht.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Link naar afspraak kopiëren",
|
||||
"copied": "Link gekopieerd",
|
||||
"copy_failed": "Kon de link niet kopiëren"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Autorisatie voltooien…",
|
||||
"completing_message": "Even geduld terwijl de verbinding tot stand wordt gebracht.",
|
||||
"received_title": "Autorisatie ontvangen",
|
||||
"received_message": "Je kunt terugkeren naar het tabblad waar je de verbinding bent gestart.",
|
||||
"failed_title": "Autorisatie mislukt",
|
||||
"denied": "Autorisatie geweigerd.",
|
||||
"failed_detail": "Autorisatie mislukt: {error}",
|
||||
"missing_params": "Autorisatiecode of status ontbreekt.",
|
||||
"delivery_failed": "Het autorisatieresultaat kon niet worden afgeleverd bij het app-tabblad. Sluit dit venster en probeer het opnieuw."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Alles doorzoeken",
|
||||
"placeholder": "Zoek in mail, contacten, agenda, bestanden...",
|
||||
"scope_label": "Zoeken in",
|
||||
"account_label": "Account",
|
||||
"scope_all": "Alles",
|
||||
"scope_mail": "E-mail",
|
||||
"scope_contacts": "Contacten",
|
||||
"scope_calendar": "Agenda",
|
||||
"scope_files": "Bestanden",
|
||||
"all_accounts": "Alle accounts",
|
||||
"show_all": "Alle resultaten tonen",
|
||||
"no_results": "Geen resultaten voor “{query}”",
|
||||
"server_hint": "Volledige woorden doorzoeken de server; woorddelen vinden al geladen items",
|
||||
"searching": "Zoeken...",
|
||||
"account_error": "Kon niet zoeken in {account}",
|
||||
"recent_searches": "Recente zoekopdrachten",
|
||||
"recurring": "Terugkerend",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Doorzoek mail, contacten, afspraken en bestanden van alle accounts. Probeer from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Plat op datum",
|
||||
"grouped": "Gegroepeerd",
|
||||
"load_more": "Meer laden",
|
||||
"preview_empty": "Selecteer een resultaat voor een voorbeeld",
|
||||
"open_full": "Openen",
|
||||
"view_label": "Weergave"
|
||||
}
|
||||
}
|
||||
|
||||
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "i {count} innych",
|
||||
"draft": "Szkic",
|
||||
"starred": "Oznaczone gwiazdką",
|
||||
"pinned": "Przypięte",
|
||||
"replied": "Odpowiedziano",
|
||||
"forwarded": "Przekazano",
|
||||
"has_attachment": "Z załącznikiem",
|
||||
"conversations_count": "{count} z {total} konwersacji",
|
||||
"conversations_count_plus": "{count}+ konwersacji",
|
||||
"conversations_count_simple": "{count} konwersacji",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Przekaż dalej",
|
||||
"delete": "Usuń",
|
||||
"archive": "Archiwizuj",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Oznacz gwiazdką",
|
||||
"unstar": "Usuń gwiazdkę",
|
||||
"mark_unread": "Oznacz jako nieprzeczytane",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Przenieś",
|
||||
"print": "Drukuj",
|
||||
"view_source": "Pokaż źródło",
|
||||
|
||||
"fullscreen": "Pełny ekran",
|
||||
|
||||
"exit_fullscreen": "Zamknij pełny ekran",
|
||||
"export_email": "Eksportuj jako .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Prześlij dalej jako załącznik",
|
||||
"import_email": "Importuj .eml lub .zip",
|
||||
"keyboard_shortcuts": "Skróty klawiszowe (?)",
|
||||
"email_source": "Źródło wiadomości",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Zażądano potwierdzenia przeczytania (kliknij, aby wyłączyć)",
|
||||
"read_receipt_off": "Zażądaj potwierdzenia przeczytania",
|
||||
"dsn_on": "Zażądano powiadomienia o dostarczeniu (kliknij, aby wyłączyć)",
|
||||
"dsn_off": "Zażądaj powiadomienia o dostarczeniu",
|
||||
"format_plain_text": "Przełącz na zwykły tekst",
|
||||
"format_rich_text": "Przełącz na tekst sformatowany (HTML)",
|
||||
"require_tls_on": "Wymagane szyfrowane dostarczenie (kliknij, aby wyłączyć)",
|
||||
"require_tls_off": "Wymagaj szyfrowanego dostarczenia (TLS)",
|
||||
"new_message": "Nowa wiadomość",
|
||||
"signature_edit_hint": "Kliknij dwukrotnie, aby edytować podpis",
|
||||
"reply": "Odpowiedz",
|
||||
@@ -631,11 +640,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Zastąp",
|
||||
"toggle_on": "Anuluj zastąpienie",
|
||||
"toggle_tooltip": "Swobodnie edytuj nazwę i adres nadawcy. Poczta jest nadal wysyłana przez twoją tożsamość - zmienia się tylko widoczny nagłówek Od.",
|
||||
"toggle_tooltip": "Swobodnie edytuj nazwę i adres nadawcy. Poczta jest nadal wysyłana przez twoją tożsamość.",
|
||||
"name_label": "Nazwa nadawcy",
|
||||
"name_placeholder": "Nazwa",
|
||||
"email_label": "Adres e-mail nadawcy",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "Jeśli twój serwer nie akceptuje tego adresu jako nadawcy koperty, zostanie użyty {identity}, a odbiorcy zobaczą go w nagłówku Return-Path."
|
||||
},
|
||||
"use_template": "Szablon",
|
||||
"save_as_template": "Zapisz jako szablon",
|
||||
@@ -648,6 +658,8 @@
|
||||
"upload_progress": "Przesyłanie {uploaded} / {total}",
|
||||
"upload_cancel": "Anuluj przesyłanie",
|
||||
"upload_failed": "Nie udało się przesłać {filename}",
|
||||
"attachment_too_large": "„{name}” jest większy, niż pozwala serwer ({max} na plik)",
|
||||
"attachments_total_too_large": "Załączniki przekroczyłyby limit serwera {max} na wiadomość",
|
||||
"drop_files": "Upuść pliki, aby załączyć",
|
||||
"show_less": "Pokaż mniej",
|
||||
"send_failed": "Nie udało się wysłać wiadomości",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Usuń kolumnę",
|
||||
"toggle_header_row": "Przełącz wiersz nagłówka",
|
||||
"delete_table": "Usuń tabelę",
|
||||
"pick_size": "Wybierz rozmiar"
|
||||
"pick_size": "Wybierz rozmiar",
|
||||
"font_size": "Rozmiar czcionki",
|
||||
"font_size_default": "Domyślny",
|
||||
"background_color": "Kolor tła",
|
||||
"remove_background_color": "Usuń kolor tła"
|
||||
},
|
||||
"send_filing_warning": "Wysłano - ale późniejsze porządkowanie nie powiodło się, może pozostać nieaktualna wersja robocza."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "Wiadomość wysłana pomyślnie",
|
||||
"email_deleted": "Wiadomość usunięta",
|
||||
"email_archived": "Wiadomość zarchiwizowana",
|
||||
"emails_deleted": "{count, plural, one {# wiadomość usunięta} few {# wiadomości usunięte} many {# wiadomości usuniętych} other {# wiadomości usuniętych}}",
|
||||
"emails_archived": "{count, plural, one {# wiadomość zarchiwizowana} few {# wiadomości zarchiwizowane} many {# wiadomości zarchiwizowanych} other {# wiadomości zarchiwizowanych}}",
|
||||
"error_archiving": "Nie udało się zarchiwizować wiadomości",
|
||||
"error_updating": "Nie udało się zaktualizować wiadomości",
|
||||
"email_starred": "Wiadomość oznaczona gwiazdką",
|
||||
"email_unstarred": "Gwiazdka usunięta",
|
||||
"email_marked_read": "Wiadomość oznaczona jako przeczytana",
|
||||
@@ -1143,7 +1163,9 @@
|
||||
"enabled": "Powiadomienia e-mail",
|
||||
"enabled_desc": "Pokazuj powiadomienia po nadejściu nowych wiadomości e-mail",
|
||||
"sound": "Dźwięk powiadomienia",
|
||||
"sound_desc": "Odtwarzaj sygnał dźwiękowy po nadejściu nowych wiadomości e-mail"
|
||||
"sound_desc": "Odtwarzaj sygnał dźwiękowy po nadejściu nowych wiadomości e-mail",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Powiadomienia kalendarza",
|
||||
@@ -1172,6 +1194,7 @@
|
||||
"status_busy": "Przetwarzanie…",
|
||||
"status_inactive": "Niewłączone na tym urządzeniu",
|
||||
"status_unsupported": "Ta przeglądarka nie obsługuje Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"title": "Powiadomienia w tle",
|
||||
"devices_title": "Zarejestrowane urządzenia",
|
||||
"devices_desc": "Wszystkie urządzenia zarejestrowane do odbioru powiadomień w tle dla tego konta. Unieważnienie natychmiast wstrzymuje ich odbiór.",
|
||||
@@ -1238,6 +1261,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Czyść wyszukiwanie przy zmianie folderu",
|
||||
"description": "Kliknięcie folderu (Odebrane, Wysłane…) czyści aktywne wyszukiwanie i otwiera ten folder. Gdy wyłączone, wyszukiwanie pozostaje i jest ponawiane w wybranym folderze."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Domyślnie żądaj potwierdzeń przeczytania",
|
||||
"description": "Włącz wcześniej żądanie potwierdzenia przeczytania podczas tworzenia nowej wiadomości."
|
||||
@@ -1321,6 +1348,12 @@
|
||||
"label": "Automatycznie wybieraj adres odpowiedzi",
|
||||
"description": "Podczas odpowiadania automatycznie przełączaj adres nadawcy na tożsamość, która pierwotnie otrzymała wiadomość"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Dopasowanie adresu odbioru",
|
||||
"description": "Które adresy odbioru są uznawane za Twoje. „Tylko dokładny adres” wybiera wyłącznie jedną ze skonfigurowanych tożsamości; „ta sama domena” traktuje dodatkowo każdy inny adres w domenach Twoich tożsamości jako alias catch-all i przepisuje na niego nagłówek Od. Wybierz dokładny adres, jeśli te adresy to listy dystrybucyjne.",
|
||||
"exact": "Tylko dokładny adres",
|
||||
"domain": "Dowolny adres w moich domenach"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Pozycja podpisu",
|
||||
"description": "Gdzie wstawić podpis w odpowiedziach i wiadomościach przekazanych dalej. Nad cytowanym tekstem brzmi naturalnie jako zakończenie odpowiedzi; pod nim zachowuje oryginalną wiadomość w całości.",
|
||||
@@ -1746,7 +1779,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "Data zakończenia musi być późniejsza niż data rozpoczęcia",
|
||||
"start_in_past": "Data rozpoczęcia jest w przeszłości",
|
||||
"empty_body": "Treść wiadomości jest pusta - odbiorcy otrzymają pustą odpowiedź"
|
||||
"empty_body": "Treść wiadomości jest pusta - odbiorcy otrzymają pustą odpowiedź",
|
||||
"subject_too_long": "Temat jest za długi dla serwera (maksymalnie {max} bajtów).",
|
||||
"body_too_long": "Wiadomość jest za długa dla serwera (maksymalnie {max} bajtów wraz z formatowaniem)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1763,6 +1798,8 @@
|
||||
"role_trash": "Kosz",
|
||||
"role_junk": "Spam / Niechciane",
|
||||
"role_archive": "Archiwum",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Brak",
|
||||
"create_folder": "Utwórz folder",
|
||||
"create_subfolder": "Utwórz podfolder",
|
||||
@@ -1882,7 +1919,9 @@
|
||||
"categories_description": "Zmień nazwy kategorii kontaktów",
|
||||
"no_categories": "Nie znaleziono kategorii",
|
||||
"group_by_letter_description": "Pokaż alfabetyczne nagłówki sekcji na liście kontaktów",
|
||||
"group_by_letter_label": "Grupuj według pierwszej litery"
|
||||
"group_by_letter_label": "Grupuj według pierwszej litery",
|
||||
"sort_by_last_name_label": "Sortuj według nazwiska",
|
||||
"sort_by_last_name_description": "Uporządkuj listę kontaktów według nazwiska, aby członkowie rodziny byli obok siebie"
|
||||
},
|
||||
"downloads": {
|
||||
"title": "Pobrane",
|
||||
@@ -1963,6 +2002,7 @@
|
||||
"actions": "Akcje",
|
||||
"add_action": "Dodaj akcję",
|
||||
"stop_processing": "Zatrzymaj przetwarzanie kolejnych reguł",
|
||||
"include_spam": "Przenoś również wiadomości oznaczone jako spam",
|
||||
"attachment_type_placeholder": "np. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Wartość (kilka oddzielonych przecinkami)",
|
||||
"condition_fields": {
|
||||
@@ -2004,6 +2044,8 @@
|
||||
"copy_to_folder": "Wybierz folder",
|
||||
"forward_to": "Prześlij dalej na adres e-mail",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "Zachowaj kopię",
|
||||
"forward_limit": "Limit przekazań na wiadomość na tym serwerze: {count}. Dodatkowe przekazania są pomijane.",
|
||||
"reject_message": "Wiadomość odrzucenia",
|
||||
"reject_placeholder": "Twoja wiadomość e-mail została odrzucona",
|
||||
"label_name": "Nazwa etykiety",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Nowy podfolder...",
|
||||
"new_folder": "Nowy folder...",
|
||||
"rename": "Zmień nazwę...",
|
||||
"share": "Udostępnij...",
|
||||
"import_email": "Importuj .eml lub .zip...",
|
||||
"empty_folder": "Opróżnij folder",
|
||||
"empty_folder_generic": "Opróżnij folder",
|
||||
@@ -2234,6 +2277,9 @@
|
||||
"toast_error_delete": "Nie udało się usunąć folderu",
|
||||
"toast_error_delete_has_children": "Folder zawiera podfoldery. Najpierw je usuń.",
|
||||
"toast_error_delete_has_email": "Folder nie jest pusty. Najpierw go opróżnij.",
|
||||
"delete_with_contents_title": "Folder nie jest pusty",
|
||||
"delete_with_contents_message": "Folder „{name}” nadal zawiera {count, plural, one {1 wiadomość} few {# wiadomości} other {# wiadomości}}. Usunąć folder wraz ze wszystkimi wiadomościami? Tej operacji nie można cofnąć.",
|
||||
"delete_with_contents_confirm": "Usuń folder i wiadomości",
|
||||
"placeholder_folder_name": "Nazwa folderu",
|
||||
"create": "Utwórz",
|
||||
"rename_confirm": "Zmień nazwę"
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Przejdź do wyszukiwania",
|
||||
"help": "Pokaż skróty",
|
||||
"refresh": "Odśwież wiadomości",
|
||||
"select_all": "Zaznacz wszystko"
|
||||
"select_all": "Zaznacz wszystko",
|
||||
"global_search": "Szukaj wszędzie"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Rozwiń/zwiń wątek"
|
||||
@@ -2721,7 +2768,10 @@
|
||||
"any_month": "Dowolny miesiąc",
|
||||
"has_email": "Z e-mailem",
|
||||
"has_phone": "Z telefonem",
|
||||
"has_photo": "Ze zdjęciem"
|
||||
"has_photo": "Ze zdjęciem",
|
||||
"sort_by": "Sortuj według",
|
||||
"sort_first_name": "Imię",
|
||||
"sort_last_name": "Nazwisko"
|
||||
},
|
||||
"open_categories": "Otwórz kategorie"
|
||||
},
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "Duplikuj",
|
||||
"today_header": "Dzisiaj",
|
||||
"tomorrow_header": "Jutro",
|
||||
"agenda_show_earlier": "Pokaż wcześniejsze wydarzenia",
|
||||
"agenda_loading": "Ładowanie…",
|
||||
"agenda_range_start": "Wydarzenia od {date}",
|
||||
"agenda_range_end": "Wydarzenia do {date}",
|
||||
"export_ics": "Eksportuj jako .ics",
|
||||
"copy_title": "Kopiuj tytuł",
|
||||
"copy_link": "Kopiuj link do spotkania",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "Kolor"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Dostępność",
|
||||
"free": "Dostępny",
|
||||
"busy": "Zajęty",
|
||||
"tentative": "Wstępnie",
|
||||
"unknown": "Nie jest użytkownikiem tego serwera",
|
||||
"checking": "Sprawdzanie…"
|
||||
},
|
||||
"title": "Uczestnicy",
|
||||
"add": "Dodaj uczestnika",
|
||||
"organizer": "Organizator",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Wszystkie wydarzenia",
|
||||
"cancel": "Anuluj",
|
||||
"save": "Zapisz",
|
||||
"delete": "Usuń"
|
||||
"delete": "Usuń",
|
||||
"rsvp_title": "Odpowiedz na wydarzenie cykliczne",
|
||||
"rsvp_description": "To jest wydarzenie cykliczne. Których wydarzeń dotyczy Twoja odpowiedź?",
|
||||
"respond": "Odpowiedz"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Przypomnienie",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Wyświetlaj godziny wydarzeń w miesięcznym widoku kalendarza",
|
||||
"show_week_numbers": "Pokaż numery tygodni",
|
||||
"show_week_numbers_desc": "Wyświetlaj numery tygodni w mini-kalendarzu",
|
||||
"calendar_free_scroll": "Swobodne przewijanie",
|
||||
"calendar_free_scroll_desc": "Przewijaj płynnie miesiące, tygodnie i dni zamiast jednego okresu naraz",
|
||||
"enable_tasks": "Włącz zadania",
|
||||
"enable_tasks_desc": "Pokaż widok zadań w kalendarzu do zarządzania zadaniami",
|
||||
"show_tasks_on_calendar": "Pokaż zadania w kalendarzu",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "Opóźnienie 1 sekundy",
|
||||
"hover_preview_delay_2s": "Opóźnienie 2 sekund",
|
||||
"hover_preview_off": "Wyłączono",
|
||||
"organizer_identity": "Organizuj zaproszenia jako",
|
||||
"organizer_identity_desc": "Adres kalendarza używany jako organizator, gdy zapraszasz osoby na wydarzenia.",
|
||||
"organizer_identity_failed": "Nie udało się zmienić adresu organizatora",
|
||||
"show_birthday_calendar": "Kalendarz urodzin kontaktów",
|
||||
"show_birthday_calendar_desc": "Pokaż wirtualny kalendarz z urodzinami z kontaktów"
|
||||
},
|
||||
@@ -2947,6 +3017,9 @@
|
||||
"alert_now": "Zaczyna się teraz",
|
||||
"alert_in_minutes": "Za {count} min",
|
||||
"invitation_sent": "Zaproszenia wysłano",
|
||||
"invitations_denied_title": "Nie można wysłać zaproszeń",
|
||||
"invitations_denied": "Serwer odmówił wysłania zaproszeń: {reason}. Zapisać wydarzenie bez ich wysyłania?",
|
||||
"save_without_invitations": "Zapisz bez zaproszeń",
|
||||
"rsvp_updated": "Odpowiedź zaktualizowano",
|
||||
"rsvp_error": "Nie udało się zaktualizować odpowiedzi",
|
||||
"event_duplicated": "Wydarzenie zduplikowano",
|
||||
@@ -2975,7 +3048,7 @@
|
||||
"supported_formats": "Obsługuje pliki iCalendar (.ics)",
|
||||
"url_description": "Wprowadź adres URL zewnętrznego kanału iCalendar (.ics), aby zaimportować wydarzenia.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "Obsługuje adresy URL CalDAV i iCalendar (.ics)",
|
||||
"url_hint": "Obsługuje adresy URL iCalendar (.ics)",
|
||||
"fetch": "Pobierz",
|
||||
"invalid_url": "Wprowadź prawidłowy adres URL",
|
||||
"url_fetch_failed": "Nie udało się pobrać kalendarza z adresu URL",
|
||||
@@ -3164,6 +3237,8 @@
|
||||
"has_attachment": "Załączniki",
|
||||
"date_after": "Po",
|
||||
"date_before": "Przed",
|
||||
"size_min": "Większe niż (KB)",
|
||||
"size_max": "Mniejsze niż (KB)",
|
||||
"starred": "Z gwiazdką",
|
||||
"unread": "Nieprzeczytane",
|
||||
"read": "Przeczytane",
|
||||
@@ -3205,6 +3280,10 @@
|
||||
"hint": "Kliknij dowolną wiadomość po lewej, aby zacząć, albo skorzystaj z przewodnika poniżej."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Edytuj dokument",
|
||||
"office_loading": "Otwieranie edytora dokumentów…",
|
||||
"office_error": "Nie udało się otworzyć edytora dokumentów.",
|
||||
"office_close": "Zamknij edytor",
|
||||
"open_in_new_tab": "Otwórz w nowej karcie",
|
||||
"title": "Pliki",
|
||||
"search_placeholder": "Szukaj plików...",
|
||||
@@ -3217,6 +3296,8 @@
|
||||
"rename": "Zmień nazwę",
|
||||
"rename_title": "Zmień nazwę",
|
||||
"new_name": "Nowa nazwa",
|
||||
"name_forbidden_chars": "Serwer nie zezwala na te znaki w nazwach: {chars}",
|
||||
"name_reserved": "Ta nazwa jest zarezerwowana na serwerze.",
|
||||
"delete": "Usuń",
|
||||
"delete_confirm_title": "Usuń zasób",
|
||||
"delete_confirm_message": "Czy na pewno chcesz usunąć \"{name}\"? Tej operacji nie można cofnąć.",
|
||||
@@ -3451,6 +3532,25 @@
|
||||
"unified_mailbox": {
|
||||
"search_unavailable": "Wyszukiwanie jest niedostępne w widoku ujednoliconym"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Ktoś",
|
||||
"invited": "{name} zaprosił(a) Cię na „{title}”",
|
||||
"updated": "{name} zaktualizował(a) „{title}”",
|
||||
"cancelled": "{name} odwołał(a) „{title}”",
|
||||
"untitled": "Wydarzenie bez tytułu"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Ktoś",
|
||||
"shared": "{name} udostępnił(a) Ci {kind} „{object}”",
|
||||
"changed": "{name} zmienił(a) Twój dostęp do {kind} „{object}”",
|
||||
"revoked": "{name} odebrał(a) Ci dostęp do {kind} „{object}”",
|
||||
"object": {
|
||||
"folder": "folder",
|
||||
"calendar": "kalendarz",
|
||||
"address_book": "książkę adresową",
|
||||
"files": "folder plików"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Udostępnij „{name}\"",
|
||||
"description": "Udziel dostępu innym użytkownikom lub grupom na tym serwerze. Zmiany są natychmiastowe.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Kopiuj link do wydarzenia",
|
||||
"copied": "Link skopiowany",
|
||||
"copy_failed": "Nie udało się skopiować linku"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Kończenie autoryzacji…",
|
||||
"completing_message": "Proszę czekać na nawiązanie połączenia.",
|
||||
"received_title": "Otrzymano autoryzację",
|
||||
"received_message": "Możesz wrócić do karty, w której rozpoczęto nawiązywanie połączenia.",
|
||||
"failed_title": "Autoryzacja nie powiodła się",
|
||||
"denied": "Autoryzacja została odrzucona.",
|
||||
"failed_detail": "Autoryzacja nie powiodła się: {error}",
|
||||
"missing_params": "Brak kodu autoryzacji lub stanu.",
|
||||
"delivery_failed": "Nie udało się przekazać wyniku autoryzacji do karty aplikacji. Zamknij to okno i spróbuj ponownie."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Szukaj wszędzie",
|
||||
"placeholder": "Szukaj w poczcie, kontaktach, kalendarzu, plikach...",
|
||||
"scope_label": "Szukaj w",
|
||||
"account_label": "Konto",
|
||||
"scope_all": "Wszystko",
|
||||
"scope_mail": "Poczta",
|
||||
"scope_contacts": "Kontakty",
|
||||
"scope_calendar": "Kalendarz",
|
||||
"scope_files": "Pliki",
|
||||
"all_accounts": "Wszystkie konta",
|
||||
"show_all": "Pokaż wszystkie wyniki",
|
||||
"no_results": "Brak wyników dla „{query}”",
|
||||
"server_hint": "Całe słowa przeszukują serwer; części słów pasują do już wczytanych elementów",
|
||||
"searching": "Szukanie...",
|
||||
"account_error": "Nie udało się przeszukać {account}",
|
||||
"recent_searches": "Ostatnie wyszukiwania",
|
||||
"recurring": "Cykliczne",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Szukaj poczty, kontaktów, wydarzeń i plików na wszystkich kontach. Wypróbuj from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Płasko według daty",
|
||||
"grouped": "Pogrupowane",
|
||||
"load_more": "Wczytaj więcej",
|
||||
"preview_empty": "Wybierz wynik, aby zobaczyć podgląd",
|
||||
"open_full": "Otwórz",
|
||||
"view_label": "Widok"
|
||||
}
|
||||
}
|
||||
|
||||
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "e {count} outros",
|
||||
"draft": "Rascunho",
|
||||
"starred": "Com Estrela",
|
||||
"pinned": "Fixado",
|
||||
"replied": "Respondido",
|
||||
"forwarded": "Encaminhado",
|
||||
"has_attachment": "Com anexo",
|
||||
"conversations_count": "{count} de {total} conversas",
|
||||
"conversations_count_plus": "{count}+ conversas",
|
||||
"conversations_count_simple": "{count} conversas",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Encaminhar",
|
||||
"delete": "Excluir",
|
||||
"archive": "Arquivar",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Adicionar Estrela",
|
||||
"unstar": "Remover Estrela",
|
||||
"mark_unread": "Marcar como não lido",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Mover",
|
||||
"print": "Imprimir",
|
||||
"view_source": "Ver código-fonte",
|
||||
|
||||
"fullscreen": "Tela cheia",
|
||||
|
||||
"exit_fullscreen": "Sair da tela cheia",
|
||||
"export_email": "Exportar como .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Encaminhar como anexo",
|
||||
"import_email": "Importar .eml ou .zip",
|
||||
"keyboard_shortcuts": "Atalhos de teclado (?)",
|
||||
"email_source": "Código-fonte do E-mail",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Confirmação de leitura solicitada (clique para desativar)",
|
||||
"read_receipt_off": "Solicitar confirmação de leitura",
|
||||
"dsn_on": "Notificação de entrega pedida (clique para desativar)",
|
||||
"dsn_off": "Pedir notificação de entrega",
|
||||
"format_plain_text": "Mudar para texto simples",
|
||||
"format_rich_text": "Mudar para texto formatado (HTML)",
|
||||
"require_tls_on": "Entrega cifrada obrigatória (clique para desativar)",
|
||||
"require_tls_off": "Exigir entrega cifrada (TLS)",
|
||||
"new_message": "Nova Mensagem",
|
||||
"signature_edit_hint": "Clique duas vezes para editar a assinatura",
|
||||
"reply": "Responder",
|
||||
@@ -631,11 +640,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Substituir",
|
||||
"toggle_on": "Cancelar substituição",
|
||||
"toggle_tooltip": "Edite livremente o nome e o endereço do remetente. O email ainda é enviado através da sua identidade - apenas o cabeçalho De visível muda.",
|
||||
"toggle_tooltip": "Edite livremente o nome e o endereço do remetente. O email ainda é enviado através da sua identidade.",
|
||||
"name_label": "Nome do remetente",
|
||||
"name_placeholder": "Nome",
|
||||
"email_label": "Endereço de email do remetente",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "Se o seu servidor não aceitar este endereço como remetente do envelope, {identity} é usado em seu lugar, e os destinatários podem vê-lo no cabeçalho Return-Path."
|
||||
},
|
||||
"use_template": "Modelo",
|
||||
"save_as_template": "Salvar como modelo",
|
||||
@@ -648,6 +658,8 @@
|
||||
"upload_progress": "Enviando {uploaded} / {total}",
|
||||
"upload_cancel": "Cancelar envio",
|
||||
"upload_failed": "Falha ao enviar {filename}",
|
||||
"attachment_too_large": "\"{name}\" é maior do que o servidor permite ({max} por ficheiro)",
|
||||
"attachments_total_too_large": "Os anexos excederiam o limite do servidor de {max} por mensagem",
|
||||
"send_failed": "Falha ao enviar o e-mail",
|
||||
"smime_sign_on": "Assinatura S/MIME ativada",
|
||||
"smime_sign_off": "Assinatura S/MIME desativada",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Excluir coluna",
|
||||
"toggle_header_row": "Alternar linha de cabeçalho",
|
||||
"delete_table": "Excluir tabela",
|
||||
"pick_size": "Escolher tamanho"
|
||||
"pick_size": "Escolher tamanho",
|
||||
"font_size": "Tamanho da fonte",
|
||||
"font_size_default": "Padrão",
|
||||
"background_color": "Cor de fundo",
|
||||
"remove_background_color": "Remover cor de fundo"
|
||||
},
|
||||
"send_filing_warning": "Enviado - mas a limpeza posterior falhou, um rascunho antigo pode permanecer."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "E-mail enviado com sucesso",
|
||||
"email_deleted": "E-mail excluído",
|
||||
"email_archived": "E-mail arquivado",
|
||||
"emails_deleted": "{count, plural, one {# e-mail excluído} other {# e-mails excluídos}}",
|
||||
"emails_archived": "{count, plural, one {# e-mail arquivado} other {# e-mails arquivados}}",
|
||||
"error_archiving": "Falha ao arquivar o e-mail",
|
||||
"error_updating": "Falha ao atualizar o e-mail",
|
||||
"email_starred": "E-mail marcado com estrela",
|
||||
"email_unstarred": "Estrela removida do e-mail",
|
||||
"email_marked_read": "E-mail marcado como lido",
|
||||
@@ -1143,7 +1163,9 @@
|
||||
"enabled": "Notificações de e-mail",
|
||||
"enabled_desc": "Mostrar notificações quando novos e-mails chegarem",
|
||||
"sound": "Som de notificação",
|
||||
"sound_desc": "Reproduzir um som quando novos e-mails chegarem"
|
||||
"sound_desc": "Reproduzir um som quando novos e-mails chegarem",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Notificações de calendário",
|
||||
@@ -1172,6 +1194,7 @@
|
||||
"status_busy": "Processando…",
|
||||
"status_inactive": "Não ativado neste dispositivo",
|
||||
"status_unsupported": "Este navegador não oferece suporte a Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"title": "Notificações em segundo plano",
|
||||
"devices_title": "Dispositivos registrados",
|
||||
"devices_desc": "Todos os dispositivos registrados para receber notificações em segundo plano desta conta. Revogar um deles interrompe o recebimento imediatamente.",
|
||||
@@ -1238,6 +1261,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Limpar a pesquisa ao mudar de pasta",
|
||||
"description": "Clicar numa pasta (Caixa de entrada, Enviados…) limpa a pesquisa ativa e mostra essa pasta. Se desativado, a pesquisa mantém-se e volta a ser executada na pasta para a qual mudar."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Solicitar confirmações de leitura por padrão",
|
||||
"description": "Ativar previamente a solicitação de confirmação de leitura ao redigir uma nova mensagem."
|
||||
@@ -1316,6 +1343,12 @@
|
||||
"label": "Selecionar automaticamente o endereço de resposta",
|
||||
"description": "Ao responder, muda automaticamente o endereço do remetente para a identidade que recebeu a mensagem original"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Correspondência do endereço de receção",
|
||||
"description": "Quais endereços de receção contam como seus. «Apenas endereço exato» escolhe somente uma das suas identidades configuradas; «mesmo domínio» trata também qualquer outro endereço dos seus domínios de identidade como alias catch-all e reescreve o cabeçalho De com ele. Escolha o endereço exato se esses endereços forem listas de distribuição.",
|
||||
"exact": "Apenas endereço exato",
|
||||
"domain": "Qualquer endereço dos meus domínios"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Posição da assinatura",
|
||||
"description": "Onde inserir a sua assinatura em respostas e encaminhamentos. Acima do texto citado lê-se naturalmente como fecho da resposta; abaixo mantém a mensagem original contígua.",
|
||||
@@ -1746,7 +1779,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "A data de fim deve ser posterior à data de início",
|
||||
"start_in_past": "A data de início está no passado",
|
||||
"empty_body": "O corpo da mensagem está vazio - os destinatários receberão uma resposta em branco"
|
||||
"empty_body": "O corpo da mensagem está vazio - os destinatários receberão uma resposta em branco",
|
||||
"subject_too_long": "O assunto é demasiado longo para o servidor (no máximo {max} bytes).",
|
||||
"body_too_long": "A mensagem é demasiado longa para o servidor (no máximo {max} bytes, incluindo a formatação)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1763,6 +1798,8 @@
|
||||
"role_trash": "Lixeira",
|
||||
"role_junk": "Spam / Lixo eletrônico",
|
||||
"role_archive": "Arquivo",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Nenhum",
|
||||
"create_folder": "Criar pasta",
|
||||
"create_subfolder": "Criar subpasta",
|
||||
@@ -1882,7 +1919,9 @@
|
||||
"categories_description": "Renomear categorias de contatos",
|
||||
"no_categories": "Nenhuma categoria encontrada",
|
||||
"group_by_letter_description": "Mostrar cabeçalhos de seção alfabéticos na lista de contatos",
|
||||
"group_by_letter_label": "Agrupar pela primeira letra"
|
||||
"group_by_letter_label": "Agrupar pela primeira letra",
|
||||
"sort_by_last_name_label": "Ordenar por sobrenome",
|
||||
"sort_by_last_name_description": "Ordenar a lista de contatos por sobrenome para que os membros da família apareçam juntos"
|
||||
},
|
||||
"downloads": {
|
||||
"title": "Downloads",
|
||||
@@ -1963,6 +2002,7 @@
|
||||
"actions": "Ações",
|
||||
"add_action": "Adicionar ação",
|
||||
"stop_processing": "Parar o processamento das regras seguintes",
|
||||
"include_spam": "Mover também as mensagens marcadas como spam",
|
||||
"attachment_type_placeholder": "ex.: pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Valor (vários separados por vírgulas)",
|
||||
"condition_fields": {
|
||||
@@ -2004,6 +2044,8 @@
|
||||
"copy_to_folder": "Selecionar pasta",
|
||||
"forward_to": "Encaminhar para endereço de e-mail",
|
||||
"forward_placeholder": "email@exemplo.com",
|
||||
"forward_keep_copy": "Manter uma cópia",
|
||||
"forward_limit": "Limite de reencaminhamentos por mensagem neste servidor: {count}. Os restantes são ignorados.",
|
||||
"reject_message": "Mensagem de rejeição",
|
||||
"reject_placeholder": "Seu e-mail foi rejeitado",
|
||||
"label_name": "Nome da etiqueta",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Nova subpasta...",
|
||||
"new_folder": "Nova pasta...",
|
||||
"rename": "Renomear...",
|
||||
"share": "Partilhar...",
|
||||
"import_email": "Importar .eml ou .zip...",
|
||||
"empty_folder": "Esvaziar pasta",
|
||||
"empty_folder_generic": "Esvaziar pasta",
|
||||
@@ -2234,6 +2277,9 @@
|
||||
"toast_error_delete": "Falha ao excluir a pasta",
|
||||
"toast_error_delete_has_children": "A pasta contém subpastas. Remova-as primeiro.",
|
||||
"toast_error_delete_has_email": "A pasta não está vazia. Esvazie-a primeiro.",
|
||||
"delete_with_contents_title": "A pasta não está vazia",
|
||||
"delete_with_contents_message": "A pasta \"{name}\" ainda contém {count, plural, one {1 mensagem} other {# mensagens}}. Eliminar a pasta e todas as suas mensagens? Esta ação não pode ser anulada.",
|
||||
"delete_with_contents_confirm": "Eliminar pasta e mensagens",
|
||||
"placeholder_folder_name": "Nome da pasta",
|
||||
"create": "Criar",
|
||||
"rename_confirm": "Renomear"
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Focar busca",
|
||||
"help": "Mostrar atalhos",
|
||||
"refresh": "Atualizar e-mails",
|
||||
"select_all": "Selecionar todos"
|
||||
"select_all": "Selecionar todos",
|
||||
"global_search": "Pesquisar em tudo"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Expandir/recolher conversa"
|
||||
@@ -2721,7 +2768,10 @@
|
||||
"any_month": "Qualquer mês",
|
||||
"has_email": "Com e-mail",
|
||||
"has_phone": "Com telefone",
|
||||
"has_photo": "Com foto"
|
||||
"has_photo": "Com foto",
|
||||
"sort_by": "Ordenar por",
|
||||
"sort_first_name": "Nome",
|
||||
"sort_last_name": "Sobrenome"
|
||||
},
|
||||
"open_categories": "Abrir categorias"
|
||||
},
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "Duplicar",
|
||||
"today_header": "Hoje",
|
||||
"tomorrow_header": "Amanhã",
|
||||
"agenda_show_earlier": "Mostrar eventos anteriores",
|
||||
"agenda_loading": "A carregar…",
|
||||
"agenda_range_start": "Eventos a partir de {date}",
|
||||
"agenda_range_end": "Eventos até {date}",
|
||||
"export_ics": "Exportar como .ics",
|
||||
"copy_title": "Copiar título",
|
||||
"copy_link": "Copiar link da reunião",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "Cor"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Disponibilidade",
|
||||
"free": "Disponível",
|
||||
"busy": "Ocupado",
|
||||
"tentative": "Provisório",
|
||||
"unknown": "Não é um utilizador deste servidor",
|
||||
"checking": "A verificar…"
|
||||
},
|
||||
"title": "Participantes",
|
||||
"add": "Adicionar participante",
|
||||
"organizer": "Organizador",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Todos os eventos",
|
||||
"cancel": "Cancelar",
|
||||
"save": "Salvar",
|
||||
"delete": "Excluir"
|
||||
"delete": "Excluir",
|
||||
"rsvp_title": "Responder a evento recorrente",
|
||||
"rsvp_description": "Este é um evento recorrente. A quais eventos sua resposta se aplica?",
|
||||
"respond": "Responder"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Lembrete",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Exibir horários dos eventos na visualização mensal do calendário",
|
||||
"show_week_numbers": "Mostrar números da semana",
|
||||
"show_week_numbers_desc": "Exibir números da semana no minicalendário",
|
||||
"calendar_free_scroll": "Deslocamento livre",
|
||||
"calendar_free_scroll_desc": "Percorrer meses, semanas e dias de forma contínua em vez de um período de cada vez",
|
||||
"enable_tasks": "Ativar tarefas",
|
||||
"enable_tasks_desc": "Mostrar uma visualização de tarefas no calendário para gerenciar pendências",
|
||||
"show_tasks_on_calendar": "Mostrar tarefas no calendário",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "Atraso de 1 segundo",
|
||||
"hover_preview_delay_2s": "Atraso de 2 segundos",
|
||||
"hover_preview_off": "Desativado",
|
||||
"organizer_identity": "Organizar convites como",
|
||||
"organizer_identity_desc": "O endereço de calendário usado como organizador quando convida pessoas para eventos.",
|
||||
"organizer_identity_failed": "Não foi possível alterar o endereço do organizador",
|
||||
"show_birthday_calendar": "Calendário de aniversários",
|
||||
"show_birthday_calendar_desc": "Mostrar um calendário virtual com os aniversários dos seus contatos"
|
||||
},
|
||||
@@ -2973,6 +3043,9 @@
|
||||
"alert_now": "Começa agora",
|
||||
"alert_in_minutes": "Em {count} min",
|
||||
"invitation_sent": "Convites enviados",
|
||||
"invitations_denied_title": "Não é possível enviar os convites",
|
||||
"invitations_denied": "O servidor recusou enviar os convites: {reason}. Guardar o evento sem os enviar?",
|
||||
"save_without_invitations": "Guardar sem convites",
|
||||
"rsvp_updated": "Resposta atualizada",
|
||||
"rsvp_error": "Falha ao atualizar resposta",
|
||||
"event_duplicated": "Evento duplicado",
|
||||
@@ -3001,7 +3074,7 @@
|
||||
"supported_formats": "Arquivos iCalendar (.ics) suportados",
|
||||
"url_description": "Insira a URL de um feed iCalendar (.ics) externo para importar eventos.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "Suporta URLs CalDAV e iCalendar (.ics)",
|
||||
"url_hint": "Suporta URLs iCalendar (.ics)",
|
||||
"fetch": "Buscar",
|
||||
"invalid_url": "Insira uma URL válida",
|
||||
"url_fetch_failed": "Não foi possível buscar o calendário da URL",
|
||||
@@ -3164,6 +3237,8 @@
|
||||
"has_attachment": "Anexos",
|
||||
"date_after": "Após",
|
||||
"date_before": "Antes de",
|
||||
"size_min": "Maior que (KB)",
|
||||
"size_max": "Menor que (KB)",
|
||||
"starred": "Favoritos",
|
||||
"unread": "Não lidos",
|
||||
"read": "Lidos",
|
||||
@@ -3205,6 +3280,10 @@
|
||||
"hint": "Clique num e-mail à esquerda para começar, ou inicie o tour."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Editar documento",
|
||||
"office_loading": "A abrir o editor de documentos…",
|
||||
"office_error": "Não foi possível abrir o editor de documentos.",
|
||||
"office_close": "Fechar editor",
|
||||
"open_in_new_tab": "Abrir num novo separador",
|
||||
"title": "Arquivos",
|
||||
"search_placeholder": "Pesquisar arquivos...",
|
||||
@@ -3217,6 +3296,8 @@
|
||||
"rename": "Renomear",
|
||||
"rename_title": "Renomear",
|
||||
"new_name": "Novo nome",
|
||||
"name_forbidden_chars": "O servidor não permite estes caracteres nos nomes: {chars}",
|
||||
"name_reserved": "Este nome está reservado no servidor.",
|
||||
"delete": "Eliminar",
|
||||
"delete_confirm_title": "Eliminar recurso",
|
||||
"delete_confirm_message": "Tem a certeza de que deseja eliminar \"{name}\"? Esta ação não pode ser desfeita.",
|
||||
@@ -3451,6 +3532,25 @@
|
||||
"unified_mailbox": {
|
||||
"search_unavailable": "A pesquisa não está disponível na vista unificada"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Alguém",
|
||||
"invited": "{name} convidou-o para \"{title}\"",
|
||||
"updated": "{name} atualizou \"{title}\"",
|
||||
"cancelled": "{name} cancelou \"{title}\"",
|
||||
"untitled": "Evento sem título"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Alguém",
|
||||
"shared": "{name} partilhou consigo {kind} \"{object}\"",
|
||||
"changed": "{name} alterou o seu acesso a {kind} \"{object}\"",
|
||||
"revoked": "{name} removeu o seu acesso a {kind} \"{object}\"",
|
||||
"object": {
|
||||
"folder": "a pasta",
|
||||
"calendar": "o calendário",
|
||||
"address_book": "o livro de endereços",
|
||||
"files": "a pasta de ficheiros"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Compartilhar \"{name}\"",
|
||||
"description": "Conceda acesso a outros usuários ou grupos neste servidor. As alterações têm efeito imediato.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Copiar link do evento",
|
||||
"copied": "Link copiado",
|
||||
"copy_failed": "Não foi possível copiar o link"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "A concluir a autorização…",
|
||||
"completing_message": "Aguarde enquanto a ligação é estabelecida.",
|
||||
"received_title": "Autorização recebida",
|
||||
"received_message": "Pode voltar ao separador onde iniciou a ligação.",
|
||||
"failed_title": "Falha na autorização",
|
||||
"denied": "A autorização foi negada.",
|
||||
"failed_detail": "Falha na autorização: {error}",
|
||||
"missing_params": "Falta o código de autorização ou o estado.",
|
||||
"delivery_failed": "Não foi possível entregar o resultado da autorização ao separador da aplicação. Feche esta janela e tente novamente."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Pesquisar em tudo",
|
||||
"placeholder": "Pesquisar e-mail, contactos, calendário, ficheiros...",
|
||||
"scope_label": "Pesquisar em",
|
||||
"account_label": "Conta",
|
||||
"scope_all": "Tudo",
|
||||
"scope_mail": "E-mail",
|
||||
"scope_contacts": "Contatos",
|
||||
"scope_calendar": "Calendário",
|
||||
"scope_files": "Arquivos",
|
||||
"all_accounts": "Todas as contas",
|
||||
"show_all": "Mostrar todos os resultados",
|
||||
"no_results": "Sem resultados para “{query}”",
|
||||
"server_hint": "Palavras completas pesquisam no servidor; palavras parciais correspondem ao que já está carregado",
|
||||
"searching": "A pesquisar...",
|
||||
"account_error": "Não foi possível pesquisar em {account}",
|
||||
"recent_searches": "Pesquisas recentes",
|
||||
"recurring": "Recorrente",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Pesquise e-mail, contactos, eventos e ficheiros em todas as contas. Experimente from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Plano por data",
|
||||
"grouped": "Agrupado",
|
||||
"load_more": "Carregar mais",
|
||||
"preview_empty": "Selecione um resultado para pré-visualizar",
|
||||
"open_full": "Abrir",
|
||||
"view_label": "Vista"
|
||||
}
|
||||
}
|
||||
|
||||
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+310
-19
@@ -6,9 +6,12 @@
|
||||
"not_starred": "Not starred"
|
||||
},
|
||||
"calendar": {
|
||||
"agenda": {
|
||||
"days_clear": "The next {count} days are clear.",
|
||||
"no_upcoming": "No upcoming events"
|
||||
"alerts": {
|
||||
"minutes_after": "{count, plural, one {# minute after} other {# minutes after}}"
|
||||
},
|
||||
"colors": {
|
||||
"indigo": "Indigo",
|
||||
"teal": "Teal"
|
||||
},
|
||||
"drawer": {
|
||||
"my_calendars": "My calendars",
|
||||
@@ -20,6 +23,8 @@
|
||||
"participant_placeholder": "Add participant by email"
|
||||
},
|
||||
"events": {
|
||||
"copy_link_short": "Link",
|
||||
"export_ics_short": ".ics",
|
||||
"tap_to_create": "Tap + to create one"
|
||||
},
|
||||
"form": {
|
||||
@@ -27,6 +32,18 @@
|
||||
"location_placeholder": "Place or address",
|
||||
"time": "Time"
|
||||
},
|
||||
"import": {
|
||||
"file_too_large_max": "That file is too large (max {size} MB).",
|
||||
"imported_count": "{count, plural, one {Imported # event.} other {Imported # events.}}",
|
||||
"no_calendar": "Create a calendar before importing events.",
|
||||
"no_calendar_title": "No calendar",
|
||||
"picker_error": "Could not open the file picker",
|
||||
"picker_unavailable": "The file picker is not installed in this build. Rebuild the app to enable importing.",
|
||||
"picker_unavailable_title": "Import unavailable",
|
||||
"refused_count": "{count, plural, one {# event could not be imported:} other {# events could not be imported:}}",
|
||||
"refused_message": "{count, plural, =1 {The event could not be imported: {reason}} one {# event could not be imported: {reason}} other {# events could not be imported: {reason}}}",
|
||||
"selected_count": "{count} selected"
|
||||
},
|
||||
"invitation": {
|
||||
"accept": "Yes",
|
||||
"add_error": "Could not add the event",
|
||||
@@ -44,6 +61,10 @@
|
||||
"response_sent": "Response sent",
|
||||
"tentative": "Maybe",
|
||||
"title": "Calendar invitation",
|
||||
"trust_authentication_failed": "This message failed sender authentication (SPF/DKIM/DMARC).",
|
||||
"trust_authentication_missing": "The sender could not be verified.",
|
||||
"trust_sender_mismatch": "The sender differs from the event organizer.",
|
||||
"trust_sender_mismatch_unverified": "The sender does not match the organizer and the message is not authenticated.",
|
||||
"trust_verified": "Sender verified"
|
||||
},
|
||||
"management": {
|
||||
@@ -56,10 +77,26 @@
|
||||
"edit_calendar": "Edit calendar",
|
||||
"new_calendar": "New calendar"
|
||||
},
|
||||
"notifications": {
|
||||
"channel_name": "Calendar reminders",
|
||||
"starts_in_days": "{count, plural, one {Starts in # day} other {Starts in # days}}",
|
||||
"starts_in_hours": "{count, plural, one {Starts in # hour} other {Starts in # hours}}",
|
||||
"starts_in_minutes": "{count, plural, one {Starts in # minute} other {Starts in # minutes}}"
|
||||
},
|
||||
"recurrence": {
|
||||
"editor_occurrences_count": "{count, plural, one {occurrence} other {occurrences}}",
|
||||
"every_count_days": "{count, plural, one {Every # day} other {Every # days}}",
|
||||
"every_count_months": "{count, plural, one {Every # month} other {Every # months}}",
|
||||
"every_count_weeks": "{count, plural, one {Every # week} other {Every # weeks}}",
|
||||
"every_count_years": "{count, plural, one {Every # year} other {Every # years}}",
|
||||
"occurrence_count": "{count, plural, one {# occurrence} other {# occurrences}}",
|
||||
"until_date": "Until {date}"
|
||||
},
|
||||
"settings": {
|
||||
"show_week_numbers_mobile_desc": "Display week numbers in the month view.",
|
||||
"time_zone": "Time zone",
|
||||
"time_zone_auto": "Device time zone",
|
||||
"time_zone_auto_zone": "Device time zone ({zone})",
|
||||
"time_zone_desc": "Zone used for new events and for interpreting the calendar. \"Device\" follows the phone."
|
||||
},
|
||||
"share": {
|
||||
@@ -67,10 +104,27 @@
|
||||
"error": "Failed to update sharing",
|
||||
"no_matches": "No matches",
|
||||
"no_principals": "Sharing is not available on this server.",
|
||||
"role_custom": "Custom",
|
||||
"search_placeholder": "Search by name or email",
|
||||
"shared_with": "Shared with",
|
||||
"title": "Share"
|
||||
"shared_with": "Shared with"
|
||||
},
|
||||
"subscription": {
|
||||
"add_feed": "Add a feed",
|
||||
"feed_too_large": "Feed is too large",
|
||||
"fetch_failed": "Could not fetch feed (HTTP {status})",
|
||||
"name_and_url_required": "Enter a name and a feed URL.",
|
||||
"not_a_feed": "That URL did not return an iCalendar feed",
|
||||
"not_synced": "Not synced yet",
|
||||
"url_scheme_invalid": "URL must start with https://, http://, webcal:// or webcals://",
|
||||
"your_subscriptions": "Your subscriptions"
|
||||
},
|
||||
"tasks": {
|
||||
"add_placeholder": "Add a task…",
|
||||
"add_time": "Add time",
|
||||
"clear_due_date": "Clear due date",
|
||||
"delete_error": "Failed to delete task",
|
||||
"not_found": "This task is no longer available.",
|
||||
"save_error": "Failed to save task",
|
||||
"update_error": "Failed to update task"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
@@ -80,11 +134,117 @@
|
||||
"create": "Create",
|
||||
"dismiss": "Dismiss",
|
||||
"done": "Done",
|
||||
"open": "Open",
|
||||
"open_link_message": "This will open {host} outside the app.",
|
||||
"open_link_title": "Open link?",
|
||||
"remove": "Remove",
|
||||
"retry": "Retry",
|
||||
"undo": "Undo",
|
||||
"update": "Update"
|
||||
},
|
||||
"contacts": {
|
||||
"address_books": {
|
||||
"confirm_delete_count": "{count, plural, =0 {Delete \"{name}\"? This cannot be undone.} one {Delete \"{name}\"? Its # contact will be deleted with it. This cannot be undone.} other {Delete \"{name}\"? Its # contacts will be deleted with it. This cannot be undone.}}"
|
||||
},
|
||||
"all_contacts": "All Contacts",
|
||||
"bulk": {
|
||||
"add_tag": "Add tag",
|
||||
"delete_confirm_mobile": "{count, plural, one {Delete # contact? This cannot be undone.} other {Delete # contacts? This cannot be undone.}}",
|
||||
"move_to_address_book": "Move to address book",
|
||||
"tag_failed": "Tagging failed"
|
||||
},
|
||||
"contact": "Contact",
|
||||
"create_hint_mobile": "Long-press to create a group instead",
|
||||
"detail": {
|
||||
"add_to_group_failed": "Add to group failed",
|
||||
"copy_name": "{name} (Copy)",
|
||||
"created_on": "Created {date}",
|
||||
"duplicate_failed": "Duplicate failed",
|
||||
"duplicate_no_book": "Cannot duplicate without an address book.",
|
||||
"no_address_book": "No address book",
|
||||
"not_found": "Contact not found",
|
||||
"open_dialer_failed": "Cannot open dialer",
|
||||
"open_link_failed": "Cannot open link",
|
||||
"open_maps_failed": "Cannot open maps",
|
||||
"open_sms_failed": "Cannot open messaging app",
|
||||
"share": "Share",
|
||||
"share_title": "Share {name}",
|
||||
"sms": "SMS",
|
||||
"timezone_value": "Timezone: {zone}",
|
||||
"updated_on": "Updated {date}"
|
||||
},
|
||||
"empty_state_hint_mobile": "Tap + to add one",
|
||||
"export": {
|
||||
"failed": "Export failed"
|
||||
},
|
||||
"form": {
|
||||
"add_nickname": "Add nickname",
|
||||
"add_note": "Add note",
|
||||
"add_organization": "Add organization",
|
||||
"address_book_required": "Select an address book to save this contact.",
|
||||
"categories_placeholder_mobile": "Add tag and press Enter",
|
||||
"context_none": "None",
|
||||
"date_placeholder": "YYYY-MM-DD, YYYY-MM, YYYY or --MM-DD",
|
||||
"display_name_optional": "Display name (optional)",
|
||||
"display_name_placeholder": "Custom display name",
|
||||
"edit_named": "Edit {name}",
|
||||
"gender_unspecified": "Unspecified",
|
||||
"grammatical_gender": "Grammatical gender",
|
||||
"invalid_date_message": "\"{date}\" is not a date. Use YYYY-MM-DD, YYYY-MM, YYYY or --MM-DD.",
|
||||
"invalid_date_title": "Invalid date",
|
||||
"invalid_email_message": "\"{address}\" is not a valid email address.",
|
||||
"invalid_email_title": "Invalid email",
|
||||
"label": "Label",
|
||||
"label_placeholder": "Work, Personal, …",
|
||||
"missing_address_book": "Missing address book",
|
||||
"missing_info": "Missing info",
|
||||
"name_or_email_required": "Add a name or at least one email.",
|
||||
"new_organization": "New organization",
|
||||
"org_name_required": "Add the organization name.",
|
||||
"photo_hint_mobile": "Tap photo to choose an image",
|
||||
"photo_permission_message": "Grant photo library permission to pick a contact photo.",
|
||||
"photo_permission_title": "Photo access needed",
|
||||
"pick_date": "Pick a date",
|
||||
"pronouns": "Pronouns",
|
||||
"pronouns_placeholder": "they/them",
|
||||
"url": "URL"
|
||||
},
|
||||
"group": "Group",
|
||||
"groups": {
|
||||
"add_member": "Add member",
|
||||
"add_members": "Add members",
|
||||
"add_members_failed": "Failed to add members",
|
||||
"create_ellipsis": "New group…",
|
||||
"delete_confirm_named": "Delete \"{name}\"? Members will not be deleted.",
|
||||
"delete_failed": "Failed to delete group",
|
||||
"email_all": "Email all",
|
||||
"members_with_count": "Members ({count})",
|
||||
"no_emails": "No emails",
|
||||
"no_members_hint": "Tap \"Add member\" to get started",
|
||||
"not_found": "Group not found",
|
||||
"remove_member": "Remove member",
|
||||
"remove_member_confirm": "Remove \"{name}\" from this group?",
|
||||
"remove_member_failed": "Failed to remove member"
|
||||
},
|
||||
"import": {
|
||||
"change_target": "Change",
|
||||
"choose_file_mobile": "Choose a vCard file",
|
||||
"failed_count": "{count, plural, one {# contact could not be imported.} other {# contacts could not be imported.}}",
|
||||
"import_count": "Import {count}",
|
||||
"into_address_book": "Import into address book",
|
||||
"into_named": "Import into {name}",
|
||||
"no_address_book": "Create an address book before importing contacts.",
|
||||
"picker_failed": "Could not open the file picker"
|
||||
},
|
||||
"no_tags_hint": "No tags yet. Type one above.",
|
||||
"picker": {
|
||||
"add_count": "{count, plural, =0 {Add} other {Add (#)}}",
|
||||
"title": "Add Contact"
|
||||
},
|
||||
"rename_tag_hint_mobile": "Long-press a tag to rename it",
|
||||
"tag_name_placeholder": "New tag name",
|
||||
"unnamed": "Unnamed"
|
||||
},
|
||||
"context_menu": {
|
||||
"color_tag": "Tag",
|
||||
"remove_color": "Remove tag"
|
||||
@@ -96,6 +256,10 @@
|
||||
"attachment_too_large": "This file is larger than the server allows ({size} > {max}).",
|
||||
"attachments_too_large_total": "The attachments would exceed the {max} this server allows per message.",
|
||||
"editor_unavailable": "Could not read the message content. Copy your text, then close and reopen the composer.",
|
||||
"format_plain_text": "Switch to plain text",
|
||||
"format_rich_text": "Switch to rich text (HTML)",
|
||||
"from_options": "Sender options",
|
||||
"group_chip": "{name} ({count})",
|
||||
"identity_unavailable": "Identity unavailable",
|
||||
"image_load_failed": "Could not load image",
|
||||
"no_recipient": "(no recipient)",
|
||||
@@ -106,7 +270,10 @@
|
||||
"recipient_copy": "Copy address",
|
||||
"recipient_move_to": "Move to {field}",
|
||||
"recipient_remove": "Remove",
|
||||
"remove_attachment": "Remove attachment",
|
||||
"save_draft": "Save Draft",
|
||||
"save_failed_no_drafts": "There is no Drafts folder to save this draft to.",
|
||||
"save_failed_no_identity": "Your sender identities could not be loaded, so this draft cannot be saved. Check your connection, or copy your text before closing.",
|
||||
"schedule_custom": "Pick date & time…",
|
||||
"schedule_in_1h": "In 1 hour",
|
||||
"schedule_in_3h": "In 3 hours",
|
||||
@@ -120,12 +287,17 @@
|
||||
"schedule_unsupported_title": "Scheduling unavailable",
|
||||
"scheduled_body": "Your message will be sent at {time}.",
|
||||
"scheduled_title": "Scheduled",
|
||||
"send_failed": "Failed to send",
|
||||
"send_now": "Send now",
|
||||
"send_timeout_body": "The message may already have gone out. Check your Sent folder before sending it again.",
|
||||
"send_timeout_title": "No answer from the server",
|
||||
"toolbar": {
|
||||
"background_color": "Background color",
|
||||
"remove_background_color": "Remove background color"
|
||||
},
|
||||
"undo_send_label": "Sending in {seconds}s…",
|
||||
"upload_failed_short": "Upload failed",
|
||||
"uploading": "Uploading...",
|
||||
"uploading_pct": "Uploading {pct}%",
|
||||
"validation": {
|
||||
"subject_required": "Add a subject"
|
||||
}
|
||||
@@ -135,6 +307,15 @@
|
||||
"emails_archived_count": "{count, plural, one {# email} other {# emails}} archived",
|
||||
"emails_moved_to": "{count, plural, one {# email} other {# emails}} moved to {mailbox}",
|
||||
"error": "Error",
|
||||
"filter_chip": "{field}: {value}",
|
||||
"filter_title": "Filter emails",
|
||||
"folder_counts": "{total} total · {unread} unread",
|
||||
"import": {
|
||||
"none": "No messages were imported.",
|
||||
"partial": "{imported} imported, {failed} failed.",
|
||||
"success": "{count, plural, one {# message imported.} other {# messages imported.}}",
|
||||
"title": "Import"
|
||||
},
|
||||
"marked_as_spam": "Marked as spam",
|
||||
"marked_as_spam_count": "{count, plural, one {# email} other {# emails}} marked as spam",
|
||||
"marked_not_spam": "Marked as not spam",
|
||||
@@ -154,10 +335,12 @@
|
||||
"outbox_discard": "Discard",
|
||||
"outbox_failed": "{count} changes could not be saved to the server.",
|
||||
"recent_searches": "Recent searches",
|
||||
"remove_filter": "Remove filter {label}",
|
||||
"scope_all_folders": "All folders",
|
||||
"scope_this_folder": "This folder",
|
||||
"search_placeholder": "Search mail...",
|
||||
"showing_cached": "Showing cached mail",
|
||||
"sort_toggle_hint": "Reverses the mail sort order",
|
||||
"swipe_move": "Move",
|
||||
"swipe_spam": "Spam",
|
||||
"total": "total",
|
||||
@@ -182,6 +365,7 @@
|
||||
"authentication": {
|
||||
"spoof_warning": "The sender address could not be verified - this message may be spoofed."
|
||||
},
|
||||
"body_truncated": "This message is too large to show in full.",
|
||||
"bundle_too_large": "The attachments are too large to bundle.",
|
||||
"color_tag": {
|
||||
"blue": "Blue",
|
||||
@@ -196,6 +380,11 @@
|
||||
},
|
||||
"delete_permanently_message": "This message will be deleted permanently and cannot be recovered.",
|
||||
"delete_permanently_title": "Delete permanently?",
|
||||
"details": {
|
||||
"attachment_count_size": "{count, plural, one {# file} other {# files}} · {size}",
|
||||
"received_after_delay": "{date} · {delay} delivery time",
|
||||
"spam_score_value": "{score} ({status})"
|
||||
},
|
||||
"export_failed": "Export failed",
|
||||
"forwarded": "Forwarded",
|
||||
"load_failed": "Failed to load email",
|
||||
@@ -221,17 +410,34 @@
|
||||
"view_light_mode": "View in light mode"
|
||||
},
|
||||
"files": {
|
||||
"delete_cascade_hint": "Folders are deleted along with everything inside them. This can't be undone.",
|
||||
"delete_item_title": "Delete this item?",
|
||||
"delete_items_title": "{count, plural, one {Delete # item?} other {Delete # items?}}",
|
||||
"done": "Done",
|
||||
"download_failed_status": "Download failed (HTTP {status})",
|
||||
"folder_empty": "This folder is empty",
|
||||
"invalid_name": "Invalid name",
|
||||
"invalid_name_slash": "Names cannot contain \"/\".",
|
||||
"move_to": "Move to…",
|
||||
"open_error": "Could not open the file",
|
||||
"open_with": "Open with…",
|
||||
"preview_too_large": "This file is too large to preview.",
|
||||
"selected_count": "{count, plural, one {# selected} other {# selected}}",
|
||||
"share_add_people": "Add people",
|
||||
"share_empty": "Not shared with anyone yet",
|
||||
"share_error": "Failed to update sharing",
|
||||
"share_file_error": "Could not share the file",
|
||||
"share_folder_hint": "Sharing a folder shares everything inside it.",
|
||||
"share_no_matches": "No matches",
|
||||
"share_no_users": "No other users found",
|
||||
"share_remove": "Remove access",
|
||||
"share_role_custom": "Custom",
|
||||
"share_search_placeholder": "Search by name or email",
|
||||
"shared_with": "Shared with"
|
||||
"share_unavailable": "Sharing is not available on this device",
|
||||
"shared_with": "Shared with",
|
||||
"sharing_access": "Sharing & access",
|
||||
"upload_unavailable": "The file picker is not installed in this build. Rebuild the app to enable uploads.",
|
||||
"uploading_progress": "Uploading {current}/{total} · {name}"
|
||||
},
|
||||
"identities": {
|
||||
"badge": {
|
||||
@@ -291,7 +497,7 @@
|
||||
"looking_up": "Looking up your server…",
|
||||
"manual": "Enter server details manually",
|
||||
"notice_bad_qr": "That code isn't a Bulwark sign-in code",
|
||||
"notice_bad_qr_detail": "Open Bulwark on the web, then Settings → Security → Link device to show one.",
|
||||
"notice_bad_qr_detail": "Open Bulwark on the web, then Settings → Security → Link Mobile App to show one.",
|
||||
"notice_bad_server": "That doesn't look like a server address",
|
||||
"notice_bad_server_detail": "Try the address you use for webmail, like mail.example.com.",
|
||||
"notice_full_email": "Enter a full email address",
|
||||
@@ -308,7 +514,7 @@
|
||||
"password_title": "Sign in to {host}",
|
||||
"provider_detail": "You'll type it on their page. Bulwark only stores a sign-in token, which you can revoke at any time.",
|
||||
"provider_title": "Your password stays with your provider",
|
||||
"qr_hint": "Settings → Security → Link device shows a code. Point the camera at it.",
|
||||
"qr_hint": "Settings → Security → Link Mobile App shows a code. Point the camera at it.",
|
||||
"qr_hint_title": "Open Bulwark on your computer",
|
||||
"qr_title": "Sign-in code",
|
||||
"scan_desc": "The fastest way in if you're signed in on the web",
|
||||
@@ -328,7 +534,18 @@
|
||||
},
|
||||
"offline": {
|
||||
"banner": "You are offline",
|
||||
"queued_changes": "{count, plural, one {# change} other {# changes}} will sync when you reconnect"
|
||||
"queued_changes": "{count, plural, one {# change} other {# changes}} will sync when you reconnect",
|
||||
"sync": {
|
||||
"cached_count": "{count, plural, one {# new message cached} other {# new messages cached}} • {size}",
|
||||
"cancelled": "Sync cancelled",
|
||||
"failed": "Offline sync failed",
|
||||
"processed": "{completed}/{total} processed",
|
||||
"ready": "Offline mail ready",
|
||||
"syncing": "Syncing offline mail",
|
||||
"title": "Offline sync",
|
||||
"unable_to_download": "Unable to download",
|
||||
"up_to_date": "Already up to date"
|
||||
}
|
||||
},
|
||||
"push_prompt": {
|
||||
"body": "Turn on background notifications so new messages reach you while the app is closed.",
|
||||
@@ -351,7 +568,7 @@
|
||||
"limit": "Maximum of {count} accounts reached."
|
||||
},
|
||||
"shared_accounts": {
|
||||
"description_mobile": "Group and shared accounts you can read through this account. Their filters and vacation responder are managed from the webmail."
|
||||
"description_manage": "Group and shared accounts you can read through this account. Select one to manage its filters and vacation responder."
|
||||
},
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
@@ -384,6 +601,15 @@
|
||||
"not_implemented": "Not implemented",
|
||||
"unavailable": "Unavailable"
|
||||
},
|
||||
"contacts": {
|
||||
"book_count": "{count, plural, one {# contact} other {# contacts}}",
|
||||
"default_book": "Default address book",
|
||||
"description_mobile": "Display preferences and address-book tools.",
|
||||
"export_description_count": "{count, plural, one {Export # contact as a single vCard file.} other {Export # contacts as a single vCard file.}}",
|
||||
"export_empty": "No contacts to export",
|
||||
"import_description_mobile": "Import contacts from a vCard (.vcf) file with duplicate detection.",
|
||||
"manage_description_mobile": "Create, rename, and remove the address books that organize your contacts."
|
||||
},
|
||||
"downloads": {
|
||||
"attachment_template": {
|
||||
"description_mobile": "Template used when saving or sharing an attachment."
|
||||
@@ -407,11 +633,23 @@
|
||||
"description_mobile": "What happens when you tap an attachment."
|
||||
},
|
||||
"auto_select_reply_identity": {
|
||||
"description_mobile": "When replying, send from the address the message was originally sent to."
|
||||
"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.",
|
||||
"external_content": {
|
||||
"allow_mobile": "Allow",
|
||||
"ask_mobile": "Ask",
|
||||
"block_mobile": "Block"
|
||||
},
|
||||
"plain_text_mode": {
|
||||
"description_mobile": "Compose and read in plain text only."
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"description": "Which received addresses count as yours. Exact address only picks one of your configured identities. Same domain also treats any other address on one of your identity domains as a catch-all alias and rewrites the From header to it. Choose exact address if those addresses are distribution lists rather than aliases.",
|
||||
"domain": "Any address on my domains",
|
||||
"exact": "Exact address only",
|
||||
"label": "Received Address Matching"
|
||||
},
|
||||
"send_delay": {
|
||||
"description_mobile": "Hold outgoing mail for a few seconds so you can cancel it. Requires server support."
|
||||
},
|
||||
@@ -426,11 +664,25 @@
|
||||
"swipe_right_action": {
|
||||
"description_mobile": "Action when you drag a row from its left edge towards the right.",
|
||||
"label_mobile": "Swipe right (left → right)"
|
||||
},
|
||||
"trusted_senders": {
|
||||
"description_mobile": "External content always loads for senders on this list.",
|
||||
"description_synced_mobile": "External content always loads for senders on this list. {count} synced via your address book.",
|
||||
"sync_add_failed": "Could not sync to address book",
|
||||
"sync_remove_failed": "Could not remove from address book",
|
||||
"synced_badge": "Synced via address book"
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
"title": "Experimental Feature"
|
||||
},
|
||||
"filters": {
|
||||
"remove_action": "Remove action",
|
||||
"remove_condition": "Remove condition",
|
||||
"sieve_editor": {
|
||||
"line_count": "{count, plural, one {# line} other {# lines}}"
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
"account": "Account",
|
||||
"cannot_delete": "Cannot delete",
|
||||
@@ -461,6 +713,7 @@
|
||||
"delete_title": "Delete identity",
|
||||
"description_mobile": "Manage sender names, email addresses, and signatures. Tap a row to edit.",
|
||||
"display_name": "Display name",
|
||||
"display_name_placeholder": "Jane Doe",
|
||||
"edit": "Edit identity",
|
||||
"email_address": "Email address",
|
||||
"email_locked": "JMAP does not allow changing an identity's email; create a new one instead.",
|
||||
@@ -502,7 +755,9 @@
|
||||
"show_folder_total_count": "Show total message count on folders",
|
||||
"show_folder_total_count_description": "Show the total next to the unread count in the folder list.",
|
||||
"swipe_mode": {
|
||||
"description": "Pick instant (swipe past the threshold to fire the action) or reveal (swipe to expose an action band, then tap to confirm).",
|
||||
"instant": "Instant (swipe = action)",
|
||||
"label": "Swipe behavior",
|
||||
"reveal": "Reveal (swipe, then tap)"
|
||||
},
|
||||
"unified_cross_account": "Unified views across accounts",
|
||||
@@ -523,22 +778,46 @@
|
||||
},
|
||||
"notifications": {
|
||||
"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.",
|
||||
"sound_channel_hint": "Sound and vibration for mail alerts are controlled by the Android notification channel: long-press a notification or open the system app settings."
|
||||
},
|
||||
"push": {
|
||||
"confirm_disable_message_mobile": "This device will stop receiving new-mail alerts for this account.",
|
||||
"description_mobile": "Receive system notifications for new mail when the app is closed. Delivered via the Bulwark push relay; the relay never sees mail content.",
|
||||
"err_android_only": "Push notifications are only available on Android.",
|
||||
"err_fcm_empty": "Firebase returned an empty device token.",
|
||||
"err_fcm_token": "Firebase could not issue a device token ({detail}). Push over FCM needs Google Play services on this device.",
|
||||
"err_fcm_token_up": "Firebase could not issue a device token ({detail}). Push over FCM needs Google Play services on this device. This device has a UnifiedPush distributor installed - switch the delivery method to UnifiedPush instead.",
|
||||
"err_jmap_refused": "The mail server refused the push subscription: {detail}",
|
||||
"err_no_account": "No account loaded - cannot set up push.",
|
||||
"err_permission": "Notification permission was not granted.",
|
||||
"err_relay_https": "The relay URL must use https://.",
|
||||
"err_relay_rejected": "The push relay rejected the registration: {detail}",
|
||||
"err_relay_unreachable": "Could not reach the push relay at {url} ({detail}).",
|
||||
"err_up_android_only": "UnifiedPush is only available on Android.",
|
||||
"err_verify_rejected": "The mail server rejected the verification code: {detail}",
|
||||
"err_verify_relay": "The relay never received the verification code from the mail server (last relay response: {detail}).",
|
||||
"err_verify_timeout": "The mail server did not send a verification code to the relay within 75 s. Check that the server can reach the relay URL.",
|
||||
"phase_account": "Account",
|
||||
"phase_distributor": "UnifiedPush distributor",
|
||||
"phase_jmap": "Mail server subscription",
|
||||
"phase_permission": "Notification permission",
|
||||
"phase_platform": "Platform",
|
||||
"phase_relay": "Relay registration",
|
||||
"phase_token": "Device token (Firebase)",
|
||||
"phase_token": "Device token",
|
||||
"phase_verify": "Verification",
|
||||
"relay_desc_mobile": "The server that delivers your notifications. Defaults to the hosted Bulwark relay; change only if you self-host. Must use https.",
|
||||
"relay_invalid": "Enter a valid https:// URL",
|
||||
"setup_failed": "Setup failed",
|
||||
"status_android_only": "Background notifications are available on Android only."
|
||||
"status_android_only": "Background notifications are available on Android only.",
|
||||
"transport_desc": "How notifications reach this device: Google’s Firebase Cloud Messaging, or a UnifiedPush distributor app (such as ntfy) for Google-free delivery.",
|
||||
"transport_fcm": "Google (FCM)",
|
||||
"transport_label": "Delivery method",
|
||||
"transport_unifiedpush": "UnifiedPush",
|
||||
"up_distributor_desc": "Several UnifiedPush distributor apps are installed - pick the one that should deliver notifications.",
|
||||
"up_distributor_label": "Distributor",
|
||||
"up_no_distributor": "No UnifiedPush distributor app is installed on this device. Install one (for example ntfy) and try again."
|
||||
}
|
||||
},
|
||||
"offline": {
|
||||
@@ -675,10 +954,21 @@
|
||||
"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."
|
||||
},
|
||||
"tabs": {
|
||||
"plugins": "Plugins"
|
||||
"plugins": "Plugins",
|
||||
"themes": "Themes",
|
||||
"updates": "Updates"
|
||||
},
|
||||
"templates": {
|
||||
"delete_title": "Delete template"
|
||||
"delete_confirm": "Permanently delete \"{name}\"?",
|
||||
"delete_title": "Delete template",
|
||||
"description_mobile": "Reusable snippets for common replies. Tap one to edit; long-press to delete.",
|
||||
"export_failed": "Export failed",
|
||||
"import_failed": "Import failed",
|
||||
"imported": "Imported",
|
||||
"imported_count": "{count, plural, one {# template} other {# templates}} added.",
|
||||
"is_html": "Body is HTML",
|
||||
"placeholders_hint": "Placeholders: {{recipient_name}}, {{company}}, {{date}}, {{day_of_week}}, {{sender_name}}",
|
||||
"share_unavailable": "Unable to share"
|
||||
},
|
||||
"themes": {
|
||||
"default_description": "The default light and dark palettes.",
|
||||
@@ -697,10 +987,11 @@
|
||||
"remove_account_hint": "Long-press to remove this account",
|
||||
"remove_account_message": "Sign out of {account} on this device. Your mail stays on the server.",
|
||||
"remove_account_title": "Remove account?",
|
||||
"show_unread": "Show unread"
|
||||
"show_unread": "Show unread",
|
||||
"tab_unavailable": "{name} (unavailable)"
|
||||
},
|
||||
"unified_mailbox": {
|
||||
"accounts_failed": "{count} account(s) could not be loaded"
|
||||
"accounts_failed": "{count, plural, one {# account could not be loaded} other {# accounts could not be loaded}}"
|
||||
},
|
||||
"updates": {
|
||||
"advisory": "Security advisory",
|
||||
|
||||
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1029
File diff suppressed because it is too large
Load Diff
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "și {count} altele",
|
||||
"draft": "Ciorne",
|
||||
"starred": "Cu stea",
|
||||
"pinned": "Fixat",
|
||||
"replied": "Răspuns",
|
||||
"forwarded": "Redirecționat",
|
||||
"has_attachment": "Cu atașament",
|
||||
"conversations_count": "{count} din conversațiile de pe {total}",
|
||||
"conversations_count_plus": "{count}+ conversations",
|
||||
"conversations_count_simple": "{count} conversații",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Redirecționează",
|
||||
"delete": "Șterge",
|
||||
"archive": "Arhivează",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Stea",
|
||||
"unstar": "Anulează marcarea cu stea",
|
||||
"mark_unread": "Marcați ca necitit",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Mutare",
|
||||
"print": "Imprimare",
|
||||
"view_source": "Vizualizați sursa",
|
||||
|
||||
"fullscreen": "Ecran complet",
|
||||
|
||||
"exit_fullscreen": "Ieșire din ecran complet",
|
||||
"export_email": "Exportați ca fișier .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Redirecționează ca atașament",
|
||||
"import_email": "Importați fișiere .eml sau .zip",
|
||||
"keyboard_shortcuts": "Comenzi rapide de la tastatură (?)",
|
||||
"email_source": "Sursa e-mailului",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Solicitare confirmare de citire (faceți clic pentru a dezactiva)",
|
||||
"read_receipt_off": "Solicitați o confirmare de citire",
|
||||
"dsn_on": "Notificare de livrare solicitată (clic pentru a dezactiva)",
|
||||
"dsn_off": "Solicită o notificare de livrare",
|
||||
"format_plain_text": "Comută la text simplu",
|
||||
"format_rich_text": "Comută la text formatat (HTML)",
|
||||
"require_tls_on": "Livrare criptată obligatorie (clic pentru a dezactiva)",
|
||||
"require_tls_off": "Impune livrare criptată (TLS)",
|
||||
"new_message": "Mesaj nou",
|
||||
"signature_edit_hint": "Dublu clic pentru a edita semnătura",
|
||||
"reply": "Răspunde",
|
||||
@@ -634,11 +643,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Suprascrieți",
|
||||
"toggle_on": "Anulare suprascriere",
|
||||
"toggle_tooltip": "Puteți modifica liber numele și adresa din câmpul „De la”. E-mailul este trimis în continuare sub identitatea dvs. – se modifică doar antetul vizibil „De la”.",
|
||||
"toggle_tooltip": "Puteți modifica liber numele și adresa din câmpul „De la”. E-mailul este trimis în continuare sub identitatea dvs.",
|
||||
"name_label": "Numele expeditorului",
|
||||
"name_placeholder": "Nume",
|
||||
"email_label": "De la adresa de e-mail",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "Dacă serverul dvs. nu acceptă această adresă ca expeditor al plicului, se folosește în schimb {identity}, iar destinatarii o pot vedea în antetul Return-Path."
|
||||
},
|
||||
"use_template": "Șablon",
|
||||
"save_as_template": "Salvează ca șablon",
|
||||
@@ -651,6 +661,8 @@
|
||||
"upload_progress": "Încărcare {uploaded} / {total}",
|
||||
"upload_cancel": "Anulează încărcarea",
|
||||
"upload_failed": "Nu s-a putut încărca {filename}",
|
||||
"attachment_too_large": "„{name}” depășește dimensiunea permisă de server ({max} per fișier)",
|
||||
"attachments_total_too_large": "Atașamentele ar depăși limita serverului de {max} per mesaj",
|
||||
"drop_files": "Trageți fișierele pentru a le atașa",
|
||||
"show_less": "Afișați mai puțin",
|
||||
"send_failed": "Nu s-a putut trimite e-mailul",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Șterge coloana",
|
||||
"toggle_header_row": "Comută rândul de antet",
|
||||
"delete_table": "Șterge tabelul",
|
||||
"pick_size": "Alege dimensiunea"
|
||||
"pick_size": "Alege dimensiunea",
|
||||
"font_size": "Dimensiune font",
|
||||
"font_size_default": "Implicit",
|
||||
"background_color": "Culoarea fundalului",
|
||||
"remove_background_color": "Elimină culoarea fundalului"
|
||||
},
|
||||
"send_filing_warning": "Trimis - dar curățarea ulterioară a eșuat, poate rămâne o ciornă veche."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "E-mailul a fost trimis cu succes",
|
||||
"email_deleted": "E-mail șters",
|
||||
"email_archived": "E-mail arhivat",
|
||||
"emails_deleted": "{count, plural, one {# e-mail șters} few {# e-mailuri șterse} other {# de e-mailuri șterse}}",
|
||||
"emails_archived": "{count, plural, one {# e-mail arhivat} few {# e-mailuri arhivate} other {# de e-mailuri arhivate}}",
|
||||
"error_archiving": "Arhivarea e-mailului a eșuat",
|
||||
"error_updating": "Actualizarea e-mailului a eșuat",
|
||||
"email_starred": "E-mail marcat cu stea",
|
||||
"email_unstarred": "E-mail fără stea",
|
||||
"email_marked_read": "E-mail marcat ca citit",
|
||||
@@ -1144,6 +1164,7 @@
|
||||
"status_active": "Activ pe acest dispozitiv",
|
||||
"status_inactive": "Nu este activat pe acest dispozitiv",
|
||||
"status_unsupported": "Acest browser nu acceptă Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"status_busy": "Se lucrează…",
|
||||
"enable": "Activați",
|
||||
"reenable": "Reînregistrează-te",
|
||||
@@ -1181,7 +1202,9 @@
|
||||
"enabled": "Notificări prin e-mail",
|
||||
"enabled_desc": "Afișați notificări la primirea de e-mailuri noi",
|
||||
"sound": "Sunet de notificare",
|
||||
"sound_desc": "Redă o alertă audio la sosirea unor e-mailuri noi"
|
||||
"sound_desc": "Redă o alertă audio la sosirea unor e-mailuri noi",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Notificări din calendar",
|
||||
@@ -1241,6 +1264,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Șterge căutarea la schimbarea folderului",
|
||||
"description": "Un clic pe un folder (Inbox, Trimise…) șterge căutarea activă și afișează acel folder. Dezactivat, căutarea rămâne și se reia în folderul ales."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Solicitați confirmări de citire în mod implicit",
|
||||
"description": "Activați în prealabil solicitarea de confirmare de citire atunci când redactați un mesaj nou."
|
||||
@@ -1324,6 +1351,12 @@
|
||||
"label": "Răspunde de la adresa de la care a fost primit mesajul",
|
||||
"description": "Când răspundeți, trimiteți mesajul de la adresa la care a fost trimis inițial. Se potrivesc mai întâi identitățile; pentru livrările de tip „catch-all” ale domeniului, antetul „De la” este rescris cu aliasul, în timp ce trimiterea se face prin identitatea dvs. principală."
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Potrivirea adresei de primire",
|
||||
"description": "Care adrese de primire sunt considerate ale tale. „Doar adresa exactă” alege numai una dintre identitățile configurate; „același domeniu” tratează și orice altă adresă de pe domeniile identităților tale ca alias catch-all și rescrie antetul De la cu ea. Alege adresa exactă dacă aceste adrese sunt liste de distribuție.",
|
||||
"exact": "Doar adresa exactă",
|
||||
"domain": "Orice adresă de pe domeniile mele"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Poziția semnăturii",
|
||||
"description": "Unde să inserați semnătura în răspunsuri și redirecționări. Deasupra textului citat, semnătura se citește în mod natural ca o încheiere a răspunsului; sub text, mesajul original rămâne contiguu.",
|
||||
@@ -1753,7 +1786,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "Data de încheiere trebuie să fie ulterioară datei de începere",
|
||||
"start_in_past": "Data de începere este în trecut",
|
||||
"empty_body": "Corpul mesajului este gol – destinatarii vor primi un răspuns gol"
|
||||
"empty_body": "Corpul mesajului este gol – destinatarii vor primi un răspuns gol",
|
||||
"subject_too_long": "Subiectul este prea lung pentru server (cel mult {max} octeți).",
|
||||
"body_too_long": "Mesajul este prea lung pentru server (cel mult {max} octeți, inclusiv formatarea)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1770,6 +1805,8 @@
|
||||
"role_trash": "Coș de gunoi",
|
||||
"role_junk": "Spam / Mesaje nedorite",
|
||||
"role_archive": "Arhivă",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Niciunul",
|
||||
"create_folder": "Creați un dosar",
|
||||
"create_subfolder": "Creați un subfolder",
|
||||
@@ -1879,6 +1916,8 @@
|
||||
"title": "Contacte",
|
||||
"description": "Importați și exportați contactele",
|
||||
"group_by_letter_label": "Grupați după prima literă",
|
||||
"sort_by_last_name_label": "Sortare după nume de familie",
|
||||
"sort_by_last_name_description": "Ordonați lista de contacte după numele de familie, astfel încât membrii familiei să apară împreună",
|
||||
"group_by_letter_description": "Afișați titlurile secțiunilor în ordine alfabetică în lista de contacte",
|
||||
"import_label": "Importați contactele",
|
||||
"import_description": "Importați contactele dintr-un fișier „vCard” (.vcf)",
|
||||
@@ -1970,6 +2009,7 @@
|
||||
"actions": "Acțiuni",
|
||||
"add_action": "Adăugare acțiune",
|
||||
"stop_processing": "Opriți procesarea regulilor următoare",
|
||||
"include_spam": "Mută și mesajele marcate ca spam",
|
||||
"attachment_type_placeholder": "de ex. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Valoare (mai multe valori separate prin virgule)",
|
||||
"condition_fields": {
|
||||
@@ -2011,6 +2051,8 @@
|
||||
"copy_to_folder": "Selectează folderul",
|
||||
"forward_to": "Redirecționează către adresa de e-mail",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "Păstrează o copie",
|
||||
"forward_limit": "Limita de redirecționări per mesaj pe acest server: {count}. Redirecționările în plus sunt omise.",
|
||||
"reject_message": "Mesaj de respingere",
|
||||
"reject_placeholder": "E-mailul dvs. a fost respins",
|
||||
"label_name": "Numele etichetei",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Subfolder nou...",
|
||||
"new_folder": "Dosar nou...",
|
||||
"rename": "Redenumire...",
|
||||
"share": "Partajează...",
|
||||
"import_email": "Importați fișiere .eml sau .zip...",
|
||||
"empty_folder": "Folder gol",
|
||||
"empty_folder_generic": "Folder gol",
|
||||
@@ -2236,7 +2279,10 @@
|
||||
"toast_error_rename": "Nu s-a putut redenumi folderul",
|
||||
"toast_error_delete": "Nu s-a putut șterge folderul",
|
||||
"toast_error_delete_has_children": "Dosarul conține subdosare. Ștergeți-le mai întâi.",
|
||||
"toast_error_delete_has_email": "Dosarul nu este gol. Goliți-l mai întâi."
|
||||
"toast_error_delete_has_email": "Dosarul nu este gol. Goliți-l mai întâi.",
|
||||
"delete_with_contents_title": "Dosarul nu este gol",
|
||||
"delete_with_contents_message": "Dosarul „{name}” încă mai conține {count, plural, one {1 mesaj} other {# mesaje}}. Ștergi dosarul și toate mesajele sale? Acțiunea nu poate fi anulată.",
|
||||
"delete_with_contents_confirm": "Șterge dosarul și mesajele"
|
||||
},
|
||||
"shortcuts": {
|
||||
"title": "Comenzi rapide de la tastatură",
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Căutare focalizată",
|
||||
"help": "Afișați comenzile rapide",
|
||||
"refresh": "Reîmprospătați e-mailurile",
|
||||
"select_all": "Selectează tot"
|
||||
"select_all": "Selectează tot",
|
||||
"global_search": "Caută în tot"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Extindeți/reduceți firul de discuție"
|
||||
@@ -2722,7 +2769,10 @@
|
||||
"any_month": "Orice lună",
|
||||
"has_email": "Are e-mail",
|
||||
"has_phone": "Are telefon",
|
||||
"has_photo": "Are fotografie"
|
||||
"has_photo": "Are fotografie",
|
||||
"sort_by": "Sortare după",
|
||||
"sort_first_name": "Prenume",
|
||||
"sort_last_name": "Nume de familie"
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "Duplicare",
|
||||
"today_header": "Astăzi",
|
||||
"tomorrow_header": "Mâine",
|
||||
"agenda_show_earlier": "Afișează evenimentele anterioare",
|
||||
"agenda_loading": "Se încarcă…",
|
||||
"agenda_range_start": "Evenimente de la {date}",
|
||||
"agenda_range_end": "Evenimente până la {date}",
|
||||
"export_ics": "Exportați ca fișier .ics",
|
||||
"copy_title": "Copiați titlul",
|
||||
"copy_link": "Copiați linkul întâlnirii",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "Culoare"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Disponibilitate",
|
||||
"free": "Disponibil",
|
||||
"busy": "Ocupat",
|
||||
"tentative": "Provizoriu",
|
||||
"unknown": "Nu este un utilizator al acestui server",
|
||||
"checking": "Se verifică…"
|
||||
},
|
||||
"title": "Participanți",
|
||||
"add": "Adăugați un participant",
|
||||
"organizer": "Organizator",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Toate evenimentele",
|
||||
"cancel": "Anulează",
|
||||
"save": "Salvare",
|
||||
"delete": "Șterge"
|
||||
"delete": "Șterge",
|
||||
"rsvp_title": "Răspundeți la evenimentul recurent",
|
||||
"rsvp_description": "Acesta este un eveniment recurent. Pentru ce evenimente se aplică răspunsul dvs.?",
|
||||
"respond": "Răspundeți"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Memento-uri",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Afișați orele evenimentelor în vizualizarea calendarului lunar",
|
||||
"show_week_numbers": "Afișați numerele săptămânilor",
|
||||
"show_week_numbers_desc": "Afișați numerele săptămânilor în mini-calendar",
|
||||
"calendar_free_scroll": "Derulare liberă",
|
||||
"calendar_free_scroll_desc": "Derulează continuu prin luni, săptămâni și zile în loc de o singură perioadă odată",
|
||||
"enable_tasks": "Activați sarcinile",
|
||||
"enable_tasks_desc": "Afișați o vizualizare a sarcinilor în calendar pentru gestionarea sarcinilor de îndeplinit",
|
||||
"show_tasks_on_calendar": "Afișați sarcinile în calendar",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "Întârziere de 1 secundă",
|
||||
"hover_preview_delay_2s": "Întârziere de 2 secunde",
|
||||
"hover_preview_off": "Dezactivat",
|
||||
"organizer_identity": "Organizează invitațiile ca",
|
||||
"organizer_identity_desc": "Adresa de calendar folosită ca organizator când inviți persoane la evenimente.",
|
||||
"organizer_identity_failed": "Nu s-a putut schimba adresa organizatorului",
|
||||
"show_birthday_calendar": "Calendarul zilelor de naștere ale contactelor",
|
||||
"show_birthday_calendar_desc": "Afișați un calendar virtual cu zilele de naștere ale contactelor dvs."
|
||||
},
|
||||
@@ -2973,6 +3043,9 @@
|
||||
"alert_now": "Începând de acum",
|
||||
"alert_in_minutes": "În {count} min",
|
||||
"invitation_sent": "Invitații trimise",
|
||||
"invitations_denied_title": "Invitațiile nu pot fi trimise",
|
||||
"invitations_denied": "Serverul a refuzat trimiterea invitațiilor: {reason}. Salvezi evenimentul fără a le trimite?",
|
||||
"save_without_invitations": "Salvează fără invitații",
|
||||
"rsvp_updated": "Răspuns actualizat",
|
||||
"rsvp_error": "Nu s-a putut actualiza răspunsul",
|
||||
"event_duplicated": "Eveniment duplicat",
|
||||
@@ -3002,7 +3075,7 @@
|
||||
"supported_formats": "Suportă fișiere de tip „iCalendar” (.ics)",
|
||||
"url_description": "Introduceți adresa URL a unui flux extern iCalendar (.ics) pentru a importa evenimente.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "Suportă adresele URL de tip CalDAV și iCalendar (.ics)",
|
||||
"url_hint": "Suportă adresele URL de tip iCalendar (.ics)",
|
||||
"fetch": "Preluare",
|
||||
"invalid_url": "Vă rugăm să introduceți o adresă de e-mail validă URL",
|
||||
"url_fetch_failed": "Nu s-a putut prelua calendarul de la URL",
|
||||
@@ -3149,6 +3222,25 @@
|
||||
"overdue": "Restant"
|
||||
}
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Cineva",
|
||||
"invited": "{name} te-a invitat la „{title}”",
|
||||
"updated": "{name} a actualizat „{title}”",
|
||||
"cancelled": "{name} a anulat „{title}”",
|
||||
"untitled": "Eveniment fără titlu"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Cineva",
|
||||
"shared": "{name} a partajat cu tine {kind} „{object}”",
|
||||
"changed": "{name} ți-a modificat accesul la {kind} „{object}”",
|
||||
"revoked": "{name} ți-a retras accesul la {kind} „{object}”",
|
||||
"object": {
|
||||
"folder": "dosarul",
|
||||
"calendar": "calendarul",
|
||||
"address_book": "agenda de contacte",
|
||||
"files": "dosarul de fișiere"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Distribuie „{name}”",
|
||||
"description": "Acordați acces altor utilizatori sau grupuri de pe acest server. Modificările intră în vigoare imediat.",
|
||||
@@ -3187,6 +3279,8 @@
|
||||
"has_attachment": "Anexe",
|
||||
"date_after": "După",
|
||||
"date_before": "Înainte",
|
||||
"size_min": "Mai mare de (KB)",
|
||||
"size_max": "Mai mic de (KB)",
|
||||
"starred": "Cu stea",
|
||||
"unread": "Necitit",
|
||||
"read": "Citiți",
|
||||
@@ -3228,6 +3322,10 @@
|
||||
"hint": "Faceți clic pe orice e-mail din partea stângă pentru a începe sau parcurgeți turul de mai jos."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Editează documentul",
|
||||
"office_loading": "Se deschide editorul de documente…",
|
||||
"office_error": "Editorul de documente nu a putut fi deschis.",
|
||||
"office_close": "Închide editorul",
|
||||
"open_in_new_tab": "Deschide într-o filă nouă",
|
||||
"title": "Fișiere",
|
||||
"search_placeholder": "Căutare fișiere...",
|
||||
@@ -3240,6 +3338,8 @@
|
||||
"rename": "Redenumire",
|
||||
"rename_title": "Redenumire",
|
||||
"new_name": "Nume nou",
|
||||
"name_forbidden_chars": "Serverul nu permite aceste caractere în nume: {chars}",
|
||||
"name_reserved": "Acest nume este rezervat pe server.",
|
||||
"delete": "Șterge",
|
||||
"delete_confirm_title": "Ștergeți resursa",
|
||||
"delete_confirm_message": "Sunteți sigur că doriți să ștergeți „{name}”? Această acțiune nu poate fi anulată.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Copiați linkul evenimentului",
|
||||
"copied": "Link copiat",
|
||||
"copy_failed": "Linkul nu a putut fi copiat"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Se finalizează autorizarea…",
|
||||
"completing_message": "Vă rugăm să așteptați stabilirea conexiunii.",
|
||||
"received_title": "Autorizare primită",
|
||||
"received_message": "Vă puteți întoarce la fila în care ați inițiat conexiunea.",
|
||||
"failed_title": "Autorizarea a eșuat",
|
||||
"denied": "Autorizarea a fost refuzată.",
|
||||
"failed_detail": "Autorizarea a eșuat: {error}",
|
||||
"missing_params": "Lipsește codul de autorizare sau starea.",
|
||||
"delivery_failed": "Rezultatul autorizării nu a putut fi transmis filei aplicației. Închideți această fereastră și încercați din nou."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Caută în tot",
|
||||
"placeholder": "Caută în mail, contacte, calendar, fișiere...",
|
||||
"scope_label": "Caută în",
|
||||
"account_label": "Cont",
|
||||
"scope_all": "Tot",
|
||||
"scope_mail": "E-mail",
|
||||
"scope_contacts": "Contacte",
|
||||
"scope_calendar": "Calendar",
|
||||
"scope_files": "Fișiere",
|
||||
"all_accounts": "Toate conturile",
|
||||
"show_all": "Afișează toate rezultatele",
|
||||
"no_results": "Niciun rezultat pentru „{query}”",
|
||||
"server_hint": "Cuvintele complete caută pe server; fragmentele de cuvinte se potrivesc cu elementele deja încărcate",
|
||||
"searching": "Se caută...",
|
||||
"account_error": "Nu s-a putut căuta în {account}",
|
||||
"recent_searches": "Căutări recente",
|
||||
"recurring": "Recurent",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Caută mail, contacte, evenimente și fișiere în toate conturile. Încearcă from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Plat după dată",
|
||||
"grouped": "Grupat",
|
||||
"load_more": "Încarcă mai multe",
|
||||
"preview_empty": "Selectează un rezultat pentru previzualizare",
|
||||
"open_full": "Deschide",
|
||||
"view_label": "Vizualizare"
|
||||
}
|
||||
}
|
||||
|
||||
+155
-15
@@ -225,6 +225,10 @@
|
||||
"and_others": "и ещё {count}",
|
||||
"draft": "Черновик",
|
||||
"starred": "Помечено",
|
||||
"pinned": "Закреплено",
|
||||
"replied": "Отвечено",
|
||||
"forwarded": "Переслано",
|
||||
"has_attachment": "С вложением",
|
||||
"conversations_count": "{count} из {total} бесед",
|
||||
"conversations_count_plus": "{count}+ бесед",
|
||||
"conversations_count_simple": "{count} бесед",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Переслать",
|
||||
"delete": "Удалить",
|
||||
"archive": "Архивировать",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Пометить",
|
||||
"unstar": "Снять пометку",
|
||||
"mark_unread": "Отметить непрочитанным",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Переместить",
|
||||
"print": "Распечатать",
|
||||
"view_source": "Просмотреть исходный код",
|
||||
|
||||
"fullscreen": "Во весь экран",
|
||||
|
||||
"exit_fullscreen": "Выйти из полноэкранного режима",
|
||||
"export_email": "Экспортировать как .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Переслать как вложение",
|
||||
"import_email": "Импортировать .eml или .zip",
|
||||
"keyboard_shortcuts": "Сочетания клавиш (?)",
|
||||
"email_source": "Исходный код письма",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Запрошено уведомление о прочтении (нажмите, чтобы отключить)",
|
||||
"read_receipt_off": "Запросить уведомление о прочтении",
|
||||
"dsn_on": "Запрошено уведомление о доставке (нажмите, чтобы отключить)",
|
||||
"dsn_off": "Запросить уведомление о доставке",
|
||||
"format_plain_text": "Переключить на простой текст",
|
||||
"format_rich_text": "Переключить на форматированный текст (HTML)",
|
||||
"require_tls_on": "Требуется зашифрованная доставка (нажмите, чтобы отключить)",
|
||||
"require_tls_off": "Требовать зашифрованную доставку (TLS)",
|
||||
"new_message": "Новое письмо",
|
||||
"signature_edit_hint": "Дважды щёлкните, чтобы изменить подпись",
|
||||
"reply": "Ответить",
|
||||
@@ -631,11 +640,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Переопределить",
|
||||
"toggle_on": "Отменить переопределение",
|
||||
"toggle_tooltip": "Свободно редактируйте имя и адрес отправителя. Письмо по-прежнему отправляется через вашу учётную запись - меняется только видимый заголовок От.",
|
||||
"toggle_tooltip": "Свободно редактируйте имя и адрес отправителя. Письмо по-прежнему отправляется через вашу учётную запись.",
|
||||
"name_label": "Имя отправителя",
|
||||
"name_placeholder": "Имя",
|
||||
"email_label": "Email отправителя",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "Если ваш сервер не принимает этот адрес как отправителя конверта, вместо него используется {identity}, и получатели увидят его в заголовке Return-Path."
|
||||
},
|
||||
"use_template": "Шаблон",
|
||||
"save_as_template": "Сохранить как шаблон",
|
||||
@@ -648,6 +658,8 @@
|
||||
"upload_progress": "Загрузка {uploaded} / {total}",
|
||||
"upload_cancel": "Отменить загрузку",
|
||||
"upload_failed": "Не удалось загрузить {filename}",
|
||||
"attachment_too_large": "«{name}» больше, чем разрешает сервер ({max} на файл)",
|
||||
"attachments_total_too_large": "Вложения превысят лимит сервера в {max} на письмо",
|
||||
"drop_files": "Перетащите файлы для прикрепления",
|
||||
"show_less": "Свернуть",
|
||||
"send_failed": "Не удалось отправить письмо",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Удалить столбец",
|
||||
"toggle_header_row": "Переключить строку заголовка",
|
||||
"delete_table": "Удалить таблицу",
|
||||
"pick_size": "Выбрать размер"
|
||||
"pick_size": "Выбрать размер",
|
||||
"font_size": "Размер шрифта",
|
||||
"font_size_default": "По умолчанию",
|
||||
"background_color": "Цвет фона",
|
||||
"remove_background_color": "Убрать цвет фона"
|
||||
},
|
||||
"send_filing_warning": "Отправлено - но последующая очистка не удалась, может остаться устаревший черновик."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "Письмо успешно отправлено",
|
||||
"email_deleted": "Письмо удалено",
|
||||
"email_archived": "Письмо архивировано",
|
||||
"emails_deleted": "{count, plural, one {# письмо удалено} few {# письма удалены} many {# писем удалено} other {# письма удалено}}",
|
||||
"emails_archived": "{count, plural, one {# письмо архивировано} few {# письма архивированы} many {# писем архивировано} other {# письма архивировано}}",
|
||||
"error_archiving": "Не удалось архивировать письмо",
|
||||
"error_updating": "Не удалось обновить письмо",
|
||||
"email_starred": "Письмо помечено",
|
||||
"email_unstarred": "Пометка снята",
|
||||
"email_marked_read": "Письмо отмечено прочитанным",
|
||||
@@ -1055,8 +1075,8 @@
|
||||
"description": "Показывает общее количество сообщений рядом с папками и метками, наряду с количеством непрочитанных. Отключите, чтобы показывать только непрочитанные."
|
||||
},
|
||||
"favicon_unread_badge": {
|
||||
"label": "Unread Count on Tab Icon",
|
||||
"description": "Show the inbox unread count as a badge on the browser tab icon, so new mail is visible when the tab is not focused. Disable to keep the icon unbadged."
|
||||
"label": "Счётчик непрочитанных на иконке вкладки и приложения",
|
||||
"description": "Показывает количество непрочитанных писем значком на иконке вкладки браузера и, если поддерживается, на иконке установленного приложения — чтобы новая почта была видна без переключения на вкладку. Отключите, чтобы обе иконки оставались без значка."
|
||||
},
|
||||
"pro_interface": {
|
||||
"label": "Pro-интерфейс (экспериментальный)",
|
||||
@@ -1143,7 +1163,9 @@
|
||||
"enabled": "Уведомления о почте",
|
||||
"enabled_desc": "Показывать уведомления при получении новых писем",
|
||||
"sound": "Звук уведомления",
|
||||
"sound_desc": "Воспроизводить звуковой сигнал при получении новых писем"
|
||||
"sound_desc": "Воспроизводить звуковой сигнал при получении новых писем",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Уведомления календаря",
|
||||
@@ -1172,6 +1194,7 @@
|
||||
"status_busy": "Выполняется…",
|
||||
"status_inactive": "Не включено на этом устройстве",
|
||||
"status_unsupported": "Этот браузер не поддерживает Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"title": "Фоновые уведомления",
|
||||
"devices_title": "Зарегистрированные устройства",
|
||||
"devices_desc": "Все устройства, зарегистрированные для получения фоновых уведомлений этой учётной записи. После отзыва устройство сразу перестаёт их получать.",
|
||||
@@ -1238,6 +1261,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Очищать поиск при переходе между папками",
|
||||
"description": "Щелчок по папке («Входящие», «Отправленные»…) очищает активный поиск и открывает эту папку. Если выключено, поиск сохраняется и выполняется заново в выбранной папке."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Запрашивать уведомления о прочтении по умолчанию",
|
||||
"description": "Заранее включать запрос уведомления о прочтении при создании нового сообщения."
|
||||
@@ -1316,6 +1343,12 @@
|
||||
"label": "Автоматически выбирать адрес для ответа",
|
||||
"description": "При ответе автоматически переключать адрес отправителя на ту учетную запись, которая получила исходное сообщение"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Сопоставление адреса получения",
|
||||
"description": "Какие адреса получения считать вашими. «Только точный адрес» выбирает исключительно одну из настроенных личностей; «тот же домен» также считает любой другой адрес на доменах ваших личностей catch-all-псевдонимом и подставляет его в заголовок «От». Выберите точный адрес, если такие адреса являются списками рассылки.",
|
||||
"exact": "Только точный адрес",
|
||||
"domain": "Любой адрес на моих доменах"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Положение подписи",
|
||||
"description": "Куда вставлять подпись в ответах и пересылке. Над цитируемым текстом она читается естественно как завершение ответа; под ним сохраняет целостность исходного сообщения.",
|
||||
@@ -1746,7 +1779,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "Дата окончания должна быть позже даты начала",
|
||||
"start_in_past": "Дата начала в прошлом",
|
||||
"empty_body": "Текст сообщения пуст - получатели получат пустой ответ"
|
||||
"empty_body": "Текст сообщения пуст - получатели получат пустой ответ",
|
||||
"subject_too_long": "Тема слишком длинная для сервера (не более {max} байт).",
|
||||
"body_too_long": "Сообщение слишком длинное для сервера (не более {max} байт с учётом форматирования)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1763,6 +1798,8 @@
|
||||
"role_trash": "Корзина",
|
||||
"role_junk": "Спам",
|
||||
"role_archive": "Архив",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Нет",
|
||||
"create_folder": "Создать папку",
|
||||
"create_subfolder": "Создать подпапку",
|
||||
@@ -1882,7 +1919,9 @@
|
||||
"categories_description": "Переименование категорий контактов",
|
||||
"no_categories": "Категории не найдены",
|
||||
"group_by_letter_description": "Показывать алфавитные заголовки разделов в списке контактов",
|
||||
"group_by_letter_label": "Группировать по первой букве"
|
||||
"group_by_letter_label": "Группировать по первой букве",
|
||||
"sort_by_last_name_label": "Сортировать по фамилии",
|
||||
"sort_by_last_name_description": "Упорядочить список контактов по фамилии, чтобы члены семьи шли подряд"
|
||||
},
|
||||
"downloads": {
|
||||
"title": "Загрузки",
|
||||
@@ -1963,6 +2002,7 @@
|
||||
"actions": "Действия",
|
||||
"add_action": "Добавить действие",
|
||||
"stop_processing": "Прекратить обработку последующих правил",
|
||||
"include_spam": "Перемещать также письма, помеченные как спам",
|
||||
"attachment_type_placeholder": "напр. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Значение (несколько через запятую)",
|
||||
"condition_fields": {
|
||||
@@ -2004,6 +2044,8 @@
|
||||
"copy_to_folder": "Выбрать папку",
|
||||
"forward_to": "Переслать на адрес электронной почты",
|
||||
"forward_placeholder": "user@пример.рф",
|
||||
"forward_keep_copy": "Сохранить копию",
|
||||
"forward_limit": "Лимит пересылок на одно письмо на этом сервере: {count}. Лишние пересылки пропускаются.",
|
||||
"reject_message": "Сообщение об отклонении",
|
||||
"reject_placeholder": "Ваше письмо было отклонено",
|
||||
"label_name": "Название тега",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Новая вложенная папка...",
|
||||
"new_folder": "Новая папка...",
|
||||
"rename": "Переименовать...",
|
||||
"share": "Поделиться...",
|
||||
"import_email": "Импортировать .eml или .zip...",
|
||||
"empty_folder": "Очистить папку",
|
||||
"empty_folder_generic": "Очистить папку",
|
||||
@@ -2234,6 +2277,9 @@
|
||||
"toast_error_delete": "Не удалось удалить папку",
|
||||
"toast_error_delete_has_children": "Папка содержит вложенные папки. Сначала удалите их.",
|
||||
"toast_error_delete_has_email": "Папка не пуста. Сначала очистите её.",
|
||||
"delete_with_contents_title": "Папка не пуста",
|
||||
"delete_with_contents_message": "Папка «{name}» всё ещё содержит {count, plural, one {# сообщение} few {# сообщения} other {# сообщений}}. Удалить папку вместе со всеми сообщениями? Это действие нельзя отменить.",
|
||||
"delete_with_contents_confirm": "Удалить папку и сообщения",
|
||||
"placeholder_folder_name": "Имя папки",
|
||||
"create": "Создать",
|
||||
"rename_confirm": "Переименовать"
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Перейти к поиску",
|
||||
"help": "Показать сочетания клавиш",
|
||||
"refresh": "Обновить письма",
|
||||
"select_all": "Выбрать все"
|
||||
"select_all": "Выбрать все",
|
||||
"global_search": "Искать везде"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Развернуть/свернуть цепочку"
|
||||
@@ -2721,7 +2768,10 @@
|
||||
"any_month": "Любой месяц",
|
||||
"has_email": "С эл. почтой",
|
||||
"has_phone": "С телефоном",
|
||||
"has_photo": "С фото"
|
||||
"has_photo": "С фото",
|
||||
"sort_by": "Сортировать по",
|
||||
"sort_first_name": "Имя",
|
||||
"sort_last_name": "Фамилия"
|
||||
},
|
||||
"open_categories": "Открыть категории"
|
||||
},
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "Дублировать",
|
||||
"today_header": "Сегодня",
|
||||
"tomorrow_header": "Завтра",
|
||||
"agenda_show_earlier": "Показать более ранние события",
|
||||
"agenda_loading": "Загрузка…",
|
||||
"agenda_range_start": "События с {date}",
|
||||
"agenda_range_end": "События до {date}",
|
||||
"export_ics": "Экспорт в .ics",
|
||||
"copy_title": "Скопировать название",
|
||||
"copy_link": "Скопировать ссылку встречи",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "Цвет"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Доступность",
|
||||
"free": "Свободен",
|
||||
"busy": "Занят",
|
||||
"tentative": "Предварительно",
|
||||
"unknown": "Не пользователь этого сервера",
|
||||
"checking": "Проверка…"
|
||||
},
|
||||
"title": "Участники",
|
||||
"add": "Добавить участника",
|
||||
"organizer": "Организатор",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Все события",
|
||||
"cancel": "Отмена",
|
||||
"save": "Сохранить",
|
||||
"delete": "Удалить"
|
||||
"delete": "Удалить",
|
||||
"rsvp_title": "Ответить на повторяющееся событие",
|
||||
"rsvp_description": "Это повторяющееся событие. К каким событиям относится ваш ответ?",
|
||||
"respond": "Ответить"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Напоминание",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Отображать время события в виде месячного календаря",
|
||||
"show_week_numbers": "Показывать номера недель",
|
||||
"show_week_numbers_desc": "Отображать номера недель в мини-календаре",
|
||||
"calendar_free_scroll": "Свободная прокрутка",
|
||||
"calendar_free_scroll_desc": "Непрерывно прокручивать месяцы, недели и дни вместо одного периода за раз",
|
||||
"enable_tasks": "Включить задачи",
|
||||
"enable_tasks_desc": "Показывать представление задач в календаре для управления делами",
|
||||
"show_tasks_on_calendar": "Показывать задачи в календаре",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "Задержка 1 секунда",
|
||||
"hover_preview_delay_2s": "Задержка 2 секунды",
|
||||
"hover_preview_off": "Отключено",
|
||||
"organizer_identity": "Организовывать приглашения как",
|
||||
"organizer_identity_desc": "Адрес календаря, используемый как организатор при приглашении людей на события.",
|
||||
"organizer_identity_failed": "Не удалось изменить адрес организатора",
|
||||
"show_birthday_calendar": "Календарь дней рождения",
|
||||
"show_birthday_calendar_desc": "Показать виртуальный календарь с днями рождения из ваших контактов"
|
||||
},
|
||||
@@ -2947,6 +3017,9 @@
|
||||
"alert_now": "Начинается сейчас",
|
||||
"alert_in_minutes": "Через {count} мин",
|
||||
"invitation_sent": "Приглашения отправлены",
|
||||
"invitations_denied_title": "Не удаётся отправить приглашения",
|
||||
"invitations_denied": "Сервер отказался отправить приглашения: {reason}. Сохранить событие без их отправки?",
|
||||
"save_without_invitations": "Сохранить без приглашений",
|
||||
"rsvp_updated": "Ответ обновлён",
|
||||
"rsvp_error": "Не удалось обновить ответ",
|
||||
"event_duplicated": "Событие дублировано",
|
||||
@@ -2975,7 +3048,7 @@
|
||||
"supported_formats": "Поддерживает файлы iCalendar (.ics)",
|
||||
"url_description": "Введите URL внешней ленты iCalendar (.ics) для импорта событий.",
|
||||
"url_placeholder": "https://пример.рф/календарь.ics",
|
||||
"url_hint": "Поддерживает URL CalDAV и iCalendar (.ics)",
|
||||
"url_hint": "Поддерживает URL iCalendar (.ics)",
|
||||
"fetch": "Получить",
|
||||
"invalid_url": "Введите корректный URL",
|
||||
"url_fetch_failed": "Не удалось получить календарь по URL",
|
||||
@@ -3164,6 +3237,8 @@
|
||||
"has_attachment": "Вложения",
|
||||
"date_after": "После",
|
||||
"date_before": "До",
|
||||
"size_min": "Больше чем (КБ)",
|
||||
"size_max": "Меньше чем (КБ)",
|
||||
"starred": "Со звёздочкой",
|
||||
"unread": "Непрочитанные",
|
||||
"read": "Прочитанные",
|
||||
@@ -3205,6 +3280,10 @@
|
||||
"hint": "Нажмите на любое письмо слева, чтобы начать, или пройдите тур ниже."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Редактировать документ",
|
||||
"office_loading": "Открытие редактора документов…",
|
||||
"office_error": "Не удалось открыть редактор документов.",
|
||||
"office_close": "Закрыть редактор",
|
||||
"open_in_new_tab": "Открыть в новой вкладке",
|
||||
"title": "Файлы",
|
||||
"search_placeholder": "Поиск файлов...",
|
||||
@@ -3217,6 +3296,8 @@
|
||||
"rename": "Переименовать",
|
||||
"rename_title": "Переименовать",
|
||||
"new_name": "Новое название",
|
||||
"name_forbidden_chars": "Сервер не допускает эти символы в именах: {chars}",
|
||||
"name_reserved": "Это имя зарезервировано на сервере.",
|
||||
"delete": "Удалить",
|
||||
"delete_confirm_title": "Удалить ресурс",
|
||||
"delete_confirm_message": "Вы уверены, что хотите удалить «{name}»? Это действие нельзя отменить.",
|
||||
@@ -3451,6 +3532,25 @@
|
||||
"unified_mailbox": {
|
||||
"search_unavailable": "Поиск недоступен в объединённом представлении"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Кто-то",
|
||||
"invited": "{name} пригласил(а) вас на «{title}»",
|
||||
"updated": "{name} обновил(а) «{title}»",
|
||||
"cancelled": "{name} отменил(а) «{title}»",
|
||||
"untitled": "Событие без названия"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Кто-то",
|
||||
"shared": "{name} поделился(ась) с вами {kind} «{object}»",
|
||||
"changed": "{name} изменил(а) ваш доступ к {kind} «{object}»",
|
||||
"revoked": "{name} отозвал(а) ваш доступ к {kind} «{object}»",
|
||||
"object": {
|
||||
"folder": "папкой",
|
||||
"calendar": "календарём",
|
||||
"address_book": "адресной книгой",
|
||||
"files": "папкой файлов"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Поделиться «{name}»",
|
||||
"description": "Предоставьте доступ другим пользователям или группам на этом сервере. Изменения вступают в силу немедленно.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Скопировать ссылку на событие",
|
||||
"copied": "Ссылка скопирована",
|
||||
"copy_failed": "Не удалось скопировать ссылку"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Завершение авторизации…",
|
||||
"completing_message": "Пожалуйста, подождите, пока устанавливается соединение.",
|
||||
"received_title": "Авторизация получена",
|
||||
"received_message": "Вы можете вернуться на вкладку, где начали подключение.",
|
||||
"failed_title": "Ошибка авторизации",
|
||||
"denied": "В авторизации отказано.",
|
||||
"failed_detail": "Ошибка авторизации: {error}",
|
||||
"missing_params": "Отсутствует код авторизации или состояние.",
|
||||
"delivery_failed": "Не удалось передать результат авторизации на вкладку приложения. Закройте это окно и попробуйте снова."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Искать везде",
|
||||
"placeholder": "Поиск по почте, контактам, календарю, файлам...",
|
||||
"scope_label": "Искать в",
|
||||
"account_label": "Аккаунт",
|
||||
"scope_all": "Всё",
|
||||
"scope_mail": "Почта",
|
||||
"scope_contacts": "Контакты",
|
||||
"scope_calendar": "Календарь",
|
||||
"scope_files": "Файлы",
|
||||
"all_accounts": "Все аккаунты",
|
||||
"show_all": "Показать все результаты",
|
||||
"no_results": "Ничего не найдено по запросу «{query}»",
|
||||
"server_hint": "Целые слова ищутся на сервере; части слов — среди уже загруженного",
|
||||
"searching": "Поиск...",
|
||||
"account_error": "Не удалось выполнить поиск в {account}",
|
||||
"recent_searches": "Недавние запросы",
|
||||
"recurring": "Повторяющееся",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Ищите почту, контакты, события и файлы во всех аккаунтах. Попробуйте from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Списком по дате",
|
||||
"grouped": "По группам",
|
||||
"load_more": "Загрузить ещё",
|
||||
"preview_empty": "Выберите результат для предпросмотра",
|
||||
"open_full": "Открыть",
|
||||
"view_label": "Вид"
|
||||
}
|
||||
}
|
||||
|
||||
+152
-12
@@ -225,6 +225,10 @@
|
||||
"and_others": "a {count} ďalších",
|
||||
"draft": "Koncept",
|
||||
"starred": "S hviezdičkou",
|
||||
"pinned": "Pripnuté",
|
||||
"replied": "Zodpovedané",
|
||||
"forwarded": "Preposlané",
|
||||
"has_attachment": "Obsahuje prílohu",
|
||||
"conversations_count": "{count} z {total} konverzácií",
|
||||
"conversations_count_plus": "{count}+ konverzácií",
|
||||
"conversations_count_simple": "{count} konverzácií",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "Preposlať",
|
||||
"delete": "Odstrániť",
|
||||
"archive": "Archivovať",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Označiť hviezdičkou",
|
||||
"unstar": "Odstrániť hviezdičku",
|
||||
"mark_unread": "Označiť ako neprečítané",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Presunúť",
|
||||
"print": "Tlačiť",
|
||||
"view_source": "Zobraziť zdrojový kód",
|
||||
|
||||
"fullscreen": "Celá obrazovka",
|
||||
|
||||
"exit_fullscreen": "Ukončiť režim celej obrazovky",
|
||||
"export_email": "Exportovať ako .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Preposlať ako prílohu",
|
||||
"import_email": "Importovať .eml alebo .zip",
|
||||
"keyboard_shortcuts": "Klávesové skratky (?)",
|
||||
"email_source": "Zdrojový kód e-mailu",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Potvrdenie o prečítaní požiadané (kliknutím vypnete)",
|
||||
"read_receipt_off": "Požiadať o potvrdenie o prečítaní",
|
||||
"dsn_on": "Oznámenie o doručení vyžiadané (kliknutím vypnete)",
|
||||
"dsn_off": "Vyžiadať oznámenie o doručení",
|
||||
"format_plain_text": "Prepnúť na obyčajný text",
|
||||
"format_rich_text": "Prepnúť na formátovaný text (HTML)",
|
||||
"require_tls_on": "Šifrované doručenie vyžadované (kliknutím vypnete)",
|
||||
"require_tls_off": "Vyžadovať šifrované doručenie (TLS)",
|
||||
"new_message": "Nová správa",
|
||||
"signature_edit_hint": "Dvojklikom upravíte podpis",
|
||||
"reply": "Odpovedať",
|
||||
@@ -638,7 +647,8 @@
|
||||
"name_label": "Meno odosielateľa",
|
||||
"name_placeholder": "Meno",
|
||||
"email_label": "E-mailová adresa odosielateľa",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "Ak váš server túto adresu neprijme ako odosielateľa obálky, použije sa namiesto nej {identity} a príjemcovia ju uvidia v hlavičke Return-Path."
|
||||
},
|
||||
"use_template": "Šablóna",
|
||||
"save_as_template": "Uložiť ako šablónu",
|
||||
@@ -651,6 +661,8 @@
|
||||
"upload_progress": "Nahrávanie {uploaded} / {total}",
|
||||
"upload_cancel": "Zrušiť nahrávanie",
|
||||
"upload_failed": "Súbor {filename} sa nepodarilo nahrať",
|
||||
"attachment_too_large": "„{name}“ je väčší, než server povoľuje ({max} na súbor)",
|
||||
"attachments_total_too_large": "Prílohy by prekročili limit servera {max} na správu",
|
||||
"drop_files": "Presuňte súbory sem na priloženie",
|
||||
"show_less": "Zobraziť menej",
|
||||
"send_failed": "Odoslanie e-mailu zlyhalo",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Odstrániť stĺpec",
|
||||
"toggle_header_row": "Prepnúť riadok záhlavia",
|
||||
"delete_table": "Odstrániť tabuľku",
|
||||
"pick_size": "Vybrať veľkosť"
|
||||
"pick_size": "Vybrať veľkosť",
|
||||
"font_size": "Veľkosť písma",
|
||||
"font_size_default": "Predvolené",
|
||||
"background_color": "Farba pozadia",
|
||||
"remove_background_color": "Odstrániť farbu pozadia"
|
||||
},
|
||||
"send_filing_warning": "Odoslané - ale následné upratovanie zlyhalo, môže zostať zastaraný koncept."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "E-mail bol úspešne odoslaný",
|
||||
"email_deleted": "E-mail bol odstránený",
|
||||
"email_archived": "E-mail bol archivovaný",
|
||||
"emails_deleted": "{count, plural, one {# e-mail odstránený} few {# e-maily odstránené} other {# e-mailov odstránených}}",
|
||||
"emails_archived": "{count, plural, one {# e-mail archivovaný} few {# e-maily archivované} other {# e-mailov archivovaných}}",
|
||||
"error_archiving": "E-mail sa nepodarilo archivovať",
|
||||
"error_updating": "E-mail sa nepodarilo aktualizovať",
|
||||
"email_starred": "E-mail bol označený hviezdičkou",
|
||||
"email_unstarred": "Hviezdička bola odstránená",
|
||||
"email_marked_read": "E-mail bol označený ako prečítaný",
|
||||
@@ -1146,7 +1166,9 @@
|
||||
"enabled": "E-mailové oznámenia",
|
||||
"enabled_desc": "Zobrazovať oznámenia pri príchode nových e-mailov",
|
||||
"sound": "Zvuk oznámenia",
|
||||
"sound_desc": "Prehrať zvukové upozornenie pri príchode nových e-mailov"
|
||||
"sound_desc": "Prehrať zvukové upozornenie pri príchode nových e-mailov",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Oznámenia kalendára",
|
||||
@@ -1168,6 +1190,7 @@
|
||||
"status_active": "Aktívne na tomto zariadení",
|
||||
"status_inactive": "Nie je povolené na tomto zariadení",
|
||||
"status_unsupported": "Tento prehliadač nepodporuje Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"status_busy": "Pracujem…",
|
||||
"enable": "Povoliť",
|
||||
"reenable": "Znovu zaregistrovať",
|
||||
@@ -1241,6 +1264,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Vymazať hľadanie pri prepnutí priečinka",
|
||||
"description": "Kliknutie na priečinok (Doručená pošta, Odoslaná…) vymaže aktívne hľadanie a zobrazí priečinok. Keď je vypnuté, hľadanie zostáva a spustí sa znova vo zvolenom priečinku."
|
||||
},
|
||||
"title": "Správanie e-mailov",
|
||||
"description": "Nakonfigurujte spôsob spracovania e-mailov",
|
||||
"request_read_receipt": {
|
||||
@@ -1324,6 +1351,12 @@
|
||||
"label": "Odpovedať z prijatej adresy",
|
||||
"description": "Pri odpovedaní automaticky prepnúť adresu odosielateľa na identitu, ktorá pôvodne prijala správu"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Porovnávanie prijímacej adresy",
|
||||
"description": "Ktoré prijímacie adresy sa považujú za vaše. „Iba presná adresa“ vyberie len jednu z vašich nastavených identít; „rovnaká doména“ navyše považuje akúkoľvek inú adresu na doménach vašich identít za catch-all alias a prepíše na ňu hlavičku Od. Ak sú tieto adresy distribučné zoznamy, zvoľte presnú adresu.",
|
||||
"exact": "Iba presná adresa",
|
||||
"domain": "Akákoľvek adresa na mojich doménach"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Pozícia podpisu",
|
||||
"description": "Kam vložiť podpis v odpovediach a preposlaniach.",
|
||||
@@ -1753,7 +1786,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "Dátum ukončenia musí byť po dátume začiatku",
|
||||
"start_in_past": "Dátum začiatku je v minulosti",
|
||||
"empty_body": "Telo správy je prázdne – príjemcovia dostanú prázdnu odpoveď"
|
||||
"empty_body": "Telo správy je prázdne – príjemcovia dostanú prázdnu odpoveď",
|
||||
"subject_too_long": "Predmet je pre server príliš dlhý (najviac {max} bajtov).",
|
||||
"body_too_long": "Správa je pre server príliš dlhá (najviac {max} bajtov vrátane formátovania)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1770,6 +1805,8 @@
|
||||
"role_trash": "Kôš",
|
||||
"role_junk": "Spam / Nevyžiadaná pošta",
|
||||
"role_archive": "Archív",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Žiadna",
|
||||
"create_folder": "Vytvoriť priečinok",
|
||||
"create_subfolder": "Vytvoriť podpriečinok",
|
||||
@@ -1879,6 +1916,8 @@
|
||||
"title": "Kontakty",
|
||||
"description": "Import a export kontaktov",
|
||||
"group_by_letter_label": "Zoskupiť podľa prvého písmena",
|
||||
"sort_by_last_name_label": "Zoradiť podľa priezviska",
|
||||
"sort_by_last_name_description": "Zoradiť zoznam kontaktov podľa priezviska, aby členovia rodiny boli pri sebe",
|
||||
"group_by_letter_description": "Zobraziť abecedné záhlavia sekcií v zozname kontaktov",
|
||||
"import_label": "Importovať kontakty",
|
||||
"import_description": "Importovať kontakty zo súboru vCard (.vcf)",
|
||||
@@ -1970,6 +2009,7 @@
|
||||
"actions": "Akcie",
|
||||
"add_action": "Pridať akciu",
|
||||
"stop_processing": "Zastaviť spracovanie ďalších pravidiel",
|
||||
"include_spam": "Presúvať aj správy označené ako spam",
|
||||
"attachment_type_placeholder": "napr. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Hodnota (viac oddelených čiarkami)",
|
||||
"condition_fields": {
|
||||
@@ -2011,6 +2051,8 @@
|
||||
"copy_to_folder": "Vybrať priečinok",
|
||||
"forward_to": "Preposlať na e-mailovú adresu",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "Ponechať kópiu",
|
||||
"forward_limit": "Limit preposlaní na správu na tomto serveri: {count}. Ďalšie preposlania sa preskočia.",
|
||||
"reject_message": "Správa o odmietnutí",
|
||||
"reject_placeholder": "Váš e-mail bol odmietnutý",
|
||||
"label_name": "Názov štítku",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Nový podpriečinok...",
|
||||
"new_folder": "Nový priečinok...",
|
||||
"rename": "Premenovať...",
|
||||
"share": "Zdieľať...",
|
||||
"import_email": "Importovať .eml alebo .zip...",
|
||||
"empty_folder": "Vyprázdniť priečinok",
|
||||
"empty_folder_generic": "Vyprázdniť priečinok",
|
||||
@@ -2236,7 +2279,10 @@
|
||||
"toast_error_rename": "Nepodarilo sa premenovať priečinok",
|
||||
"toast_error_delete": "Nepodarilo sa zmazať priečinok",
|
||||
"toast_error_delete_has_children": "Priečinok obsahuje podpriečinky. Najprv ich odstráňte.",
|
||||
"toast_error_delete_has_email": "Priečinok nie je prázdny. Najprv ho vyprázdnite."
|
||||
"toast_error_delete_has_email": "Priečinok nie je prázdny. Najprv ho vyprázdnite.",
|
||||
"delete_with_contents_title": "Priečinok nie je prázdny",
|
||||
"delete_with_contents_message": "Priečinok „{name}“ stále obsahuje {count, plural, one {1 správu} few {# správy} other {# správ}}. Odstrániť priečinok aj všetky jeho správy? Túto akciu nie je možné vrátiť späť.",
|
||||
"delete_with_contents_confirm": "Odstrániť priečinok a správy"
|
||||
},
|
||||
"shortcuts": {
|
||||
"title": "Klávesové skratky",
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Prejsť na hľadanie",
|
||||
"help": "Zobraziť skratky",
|
||||
"refresh": "Obnoviť e-maily",
|
||||
"select_all": "Vybrať všetko"
|
||||
"select_all": "Vybrať všetko",
|
||||
"global_search": "Hľadať všade"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Rozbaliť/zbaliť vlákno"
|
||||
@@ -2722,7 +2769,10 @@
|
||||
"any_month": "Ktorýkoľvek mesiac",
|
||||
"has_email": "Má e-mail",
|
||||
"has_phone": "Má telefón",
|
||||
"has_photo": "Má fotku"
|
||||
"has_photo": "Má fotku",
|
||||
"sort_by": "Zoradiť podľa",
|
||||
"sort_first_name": "Meno",
|
||||
"sort_last_name": "Priezvisko"
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "Duplikovať",
|
||||
"today_header": "Dnes",
|
||||
"tomorrow_header": "Zajtra",
|
||||
"agenda_show_earlier": "Zobraziť skoršie udalosti",
|
||||
"agenda_loading": "Načítava sa…",
|
||||
"agenda_range_start": "Udalosti od {date}",
|
||||
"agenda_range_end": "Udalosti do {date}",
|
||||
"export_ics": "Exportovať ako .ics",
|
||||
"copy_title": "Kopírovať názov",
|
||||
"copy_link": "Kopírovať odkaz na stretnutie",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "Farba"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Dostupnosť",
|
||||
"free": "Voľný",
|
||||
"busy": "Zaneprázdnený",
|
||||
"tentative": "Predbežne",
|
||||
"unknown": "Nie je používateľom tohto servera",
|
||||
"checking": "Overuje sa…"
|
||||
},
|
||||
"title": "Účastníci",
|
||||
"add": "Pridať účastníka",
|
||||
"organizer": "Organizátor",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Všetky udalosti",
|
||||
"cancel": "Zrušiť",
|
||||
"save": "Uložiť",
|
||||
"delete": "Odstrániť"
|
||||
"delete": "Odstrániť",
|
||||
"rsvp_title": "Odpovedať na opakujúcu sa udalosť",
|
||||
"rsvp_description": "Toto je opakujúca sa udalosť. Na ktoré udalosti sa vzťahuje vaša odpoveď?",
|
||||
"respond": "Odpovedať"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Pripomenutia",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Zobrazovať časy udalostí v mesačnom zobrazení kalendára",
|
||||
"show_week_numbers": "Zobraziť čísla týždňov",
|
||||
"show_week_numbers_desc": "Zobrazovať čísla týždňov v minikalendári",
|
||||
"calendar_free_scroll": "Voľné posúvanie",
|
||||
"calendar_free_scroll_desc": "Plynulo prechádzať mesiace, týždne a dni namiesto jedného obdobia naraz",
|
||||
"enable_tasks": "Povoliť úlohy",
|
||||
"enable_tasks_desc": "Zobrazovať zobrazenie úloh v kalendári",
|
||||
"show_tasks_on_calendar": "Zobraziť úlohy v kalendári",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "Oneskorenie 1 sekunda",
|
||||
"hover_preview_delay_2s": "Oneskorenie 2 sekundy",
|
||||
"hover_preview_off": "Vypnuté",
|
||||
"organizer_identity": "Organizovať pozvánky ako",
|
||||
"organizer_identity_desc": "Adresa kalendára použitá ako organizátor, keď pozývate ľudí na udalosti.",
|
||||
"organizer_identity_failed": "Nepodarilo sa zmeniť adresu organizátora",
|
||||
"show_birthday_calendar": "Kalendár narodenín",
|
||||
"show_birthday_calendar_desc": "Zobrazovať virtuálny kalendár s narodeninami z vašich kontaktov"
|
||||
},
|
||||
@@ -2973,6 +3043,9 @@
|
||||
"alert_now": "Začína teraz",
|
||||
"alert_in_minutes": "Za {count} min",
|
||||
"invitation_sent": "Pozvánky boli odoslané",
|
||||
"invitations_denied_title": "Pozvánky nie je možné odoslať",
|
||||
"invitations_denied": "Server odmietol odoslať pozvánky: {reason}. Uložiť udalosť bez ich odoslania?",
|
||||
"save_without_invitations": "Uložiť bez pozvánok",
|
||||
"rsvp_updated": "Odpoveď bola aktualizovaná",
|
||||
"rsvp_error": "Aktualizácia odpovede zlyhala",
|
||||
"event_duplicated": "Udalosť bola duplikovaná",
|
||||
@@ -3002,7 +3075,7 @@
|
||||
"supported_formats": "Podporuje súbory iCalendar (.ics)",
|
||||
"url_description": "Zadajte URL adresu externého zdroja iCalendar (.ics) na import udalostí.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "Podporuje URL adresy CalDAV a iCalendar (.ics)",
|
||||
"url_hint": "Podporuje URL adresy iCalendar (.ics)",
|
||||
"fetch": "Načítať",
|
||||
"invalid_url": "Zadajte platnú URL adresu",
|
||||
"url_fetch_failed": "Načítanie kalendára z URL adresy zlyhalo",
|
||||
@@ -3149,6 +3222,25 @@
|
||||
"overdue": "Po termíne"
|
||||
}
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Niekto",
|
||||
"invited": "{name} vás pozval(a) na „{title}“",
|
||||
"updated": "{name} aktualizoval(a) „{title}“",
|
||||
"cancelled": "{name} zrušil(a) „{title}“",
|
||||
"untitled": "Udalosť bez názvu"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Niekto",
|
||||
"shared": "{name} s vami zdieľa {kind} „{object}“",
|
||||
"changed": "{name} zmenil(a) váš prístup k {kind} „{object}“",
|
||||
"revoked": "{name} odobral(a) váš prístup k {kind} „{object}“",
|
||||
"object": {
|
||||
"folder": "priečinok",
|
||||
"calendar": "kalendár",
|
||||
"address_book": "adresár",
|
||||
"files": "priečinok súborov"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Zdieľať \"{name}\"",
|
||||
"description": "Udeľte prístup ďalším používateľom alebo skupinám na tomto serveri. Zmeny sa prejavia okamžite.",
|
||||
@@ -3187,6 +3279,8 @@
|
||||
"has_attachment": "Prílohy",
|
||||
"date_after": "Po",
|
||||
"date_before": "Pred",
|
||||
"size_min": "Väčšie ako (KB)",
|
||||
"size_max": "Menšie ako (KB)",
|
||||
"starred": "S hviezdičkou",
|
||||
"unread": "Neprečítané",
|
||||
"read": "Prečítané",
|
||||
@@ -3228,6 +3322,10 @@
|
||||
"hint": "Kliknite na ľubovoľný e-mail vľavo na začiatok alebo využite sprievodcu nižšie."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Upraviť dokument",
|
||||
"office_loading": "Otvára sa editor dokumentov…",
|
||||
"office_error": "Editor dokumentov sa nepodarilo otvoriť.",
|
||||
"office_close": "Zavrieť editor",
|
||||
"open_in_new_tab": "Otvoriť v novej karte",
|
||||
"title": "Súbory",
|
||||
"search_placeholder": "Hľadať súbory...",
|
||||
@@ -3240,6 +3338,8 @@
|
||||
"rename": "Premenovať",
|
||||
"rename_title": "Premenovať",
|
||||
"new_name": "Nový názov",
|
||||
"name_forbidden_chars": "Server nepovoľuje v názvoch tieto znaky: {chars}",
|
||||
"name_reserved": "Tento názov je na serveri vyhradený.",
|
||||
"delete": "Odstrániť",
|
||||
"delete_confirm_title": "Odstrániť položku",
|
||||
"delete_confirm_message": "Naozaj chcete odstrániť \"{name}\"? Túto akciu nie je možné vrátiť.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Kopírovať odkaz na udalosť",
|
||||
"copied": "Odkaz skopírovaný",
|
||||
"copy_failed": "Odkaz sa nepodarilo skopírovať"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Dokončovanie autorizácie…",
|
||||
"completing_message": "Počkajte, kým sa vytvorí spojenie.",
|
||||
"received_title": "Autorizácia prijatá",
|
||||
"received_message": "Môžete sa vrátiť na kartu, kde ste začali pripájanie.",
|
||||
"failed_title": "Autorizácia zlyhala",
|
||||
"denied": "Autorizácia bola zamietnutá.",
|
||||
"failed_detail": "Autorizácia zlyhala: {error}",
|
||||
"missing_params": "Chýba autorizačný kód alebo stav.",
|
||||
"delivery_failed": "Výsledok autorizácie sa nepodarilo odovzdať karte aplikácie. Zavrite toto okno a skúste to znova."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Hľadať všade",
|
||||
"placeholder": "Hľadať v pošte, kontaktoch, kalendári, súboroch...",
|
||||
"scope_label": "Hľadať v",
|
||||
"account_label": "Účet",
|
||||
"scope_all": "Všetko",
|
||||
"scope_mail": "Pošta",
|
||||
"scope_contacts": "Kontakty",
|
||||
"scope_calendar": "Kalendár",
|
||||
"scope_files": "Súbory",
|
||||
"all_accounts": "Všetky účty",
|
||||
"show_all": "Zobraziť všetky výsledky",
|
||||
"no_results": "Žiadne výsledky pre „{query}“",
|
||||
"server_hint": "Celé slová prehľadávajú server; časti slov zodpovedajú už načítaným položkám",
|
||||
"searching": "Hľadá sa...",
|
||||
"account_error": "Nepodarilo sa prehľadať {account}",
|
||||
"recent_searches": "Nedávne hľadania",
|
||||
"recurring": "Opakovaná",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Hľadajte poštu, kontakty, udalosti a súbory vo všetkých účtoch. Skúste from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Plocho podľa dátumu",
|
||||
"grouped": "Zoskupené",
|
||||
"load_more": "Načítať ďalšie",
|
||||
"preview_empty": "Vyberte výsledok pre náhľad",
|
||||
"open_full": "Otvoriť",
|
||||
"view_label": "Zobrazenie"
|
||||
}
|
||||
}
|
||||
|
||||
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "ve {count} diğer",
|
||||
"draft": "Taslak",
|
||||
"starred": "Yıldızlı",
|
||||
"pinned": "Sabitlendi",
|
||||
"replied": "Yanıtlandı",
|
||||
"forwarded": "İletildi",
|
||||
"has_attachment": "Ek içeriyor",
|
||||
"conversations_count": "{total} konuşmadan {count} tanesi",
|
||||
"conversations_count_plus": "{count}+ konuşma",
|
||||
"conversations_count_simple": "{count} konuşma",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "İlet",
|
||||
"delete": "Sil",
|
||||
"archive": "Arşivle",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "Yıldızla",
|
||||
"unstar": "Yıldızı Kaldır",
|
||||
"mark_unread": "Okunmadı olarak işaretle",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Taşı",
|
||||
"print": "Yazdır",
|
||||
"view_source": "Kaynağı görüntüle",
|
||||
|
||||
"fullscreen": "Tam ekran",
|
||||
|
||||
"exit_fullscreen": "Tam ekrandan çık",
|
||||
"export_email": ".eml olarak dışa aktar",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Ek olarak ilet",
|
||||
"import_email": ".eml veya .zip içe aktar",
|
||||
"keyboard_shortcuts": "Klavye kısayolları (?)",
|
||||
"email_source": "E-posta Kaynağı",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Okundu bilgisi istendi (devre dışı bırakmak için tıklayın)",
|
||||
"read_receipt_off": "Okundu bilgisi iste",
|
||||
"dsn_on": "Teslimat bildirimi istendi (kapatmak için tıklayın)",
|
||||
"dsn_off": "Teslimat bildirimi iste",
|
||||
"format_plain_text": "Düz metne geç",
|
||||
"format_rich_text": "Zengin metne (HTML) geç",
|
||||
"require_tls_on": "Şifreli teslimat zorunlu (kapatmak için tıklayın)",
|
||||
"require_tls_off": "Şifreli teslimat zorunlu kıl (TLS)",
|
||||
"new_message": "Yeni İleti",
|
||||
"signature_edit_hint": "İmzayı düzenlemek için çift tıklayın",
|
||||
"reply": "Yanıtla",
|
||||
@@ -634,11 +643,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Geçersiz kıl",
|
||||
"toggle_on": "Geçersiz kılmayı iptal et",
|
||||
"toggle_tooltip": "Gönderen adını ve adresini serbestçe düzenleyin. Posta hâlâ kimliğiniz üzerinden gönderilir - yalnızca görünür Kimden başlığı değişir.",
|
||||
"toggle_tooltip": "Gönderen adını ve adresini serbestçe düzenleyin. Posta hâlâ kimliğiniz üzerinden gönderilir.",
|
||||
"name_label": "Gönderen adı",
|
||||
"name_placeholder": "Ad",
|
||||
"email_label": "Gönderen e-posta adresi",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "Sunucunuz bu adresi zarf göndereni olarak kabul etmezse yerine {identity} kullanılır ve alıcılar bunu Return-Path başlığında görebilir."
|
||||
},
|
||||
"use_template": "Şablon",
|
||||
"save_as_template": "Şablon Olarak Kaydet",
|
||||
@@ -651,6 +661,8 @@
|
||||
"upload_progress": "{uploaded} / {total} yükleniyor",
|
||||
"upload_cancel": "Yüklemeyi iptal et",
|
||||
"upload_failed": "{filename} yüklenemedi",
|
||||
"attachment_too_large": "\"{name}\" sunucunun izin verdiğinden büyük (dosya başına {max})",
|
||||
"attachments_total_too_large": "Ekler sunucunun ileti başına {max} sınırını aşar",
|
||||
"drop_files": "Eklemek için dosyaları buraya bırakın",
|
||||
"show_less": "Daha az göster",
|
||||
"send_failed": "E-posta gönderilemedi",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Sütunu sil",
|
||||
"toggle_header_row": "Başlık satırını aç/kapat",
|
||||
"delete_table": "Tabloyu sil",
|
||||
"pick_size": "Boyut seç"
|
||||
"pick_size": "Boyut seç",
|
||||
"font_size": "Yazı boyutu",
|
||||
"font_size_default": "Varsayılan",
|
||||
"background_color": "Arka plan rengi",
|
||||
"remove_background_color": "Arka plan rengini kaldır"
|
||||
},
|
||||
"send_filing_warning": "Gönderildi - ancak sonrasındaki temizleme başarısız oldu, eski bir taslak kalabilir."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "E-posta başarıyla gönderildi",
|
||||
"email_deleted": "E-posta silindi",
|
||||
"email_archived": "E-posta arşivlendi",
|
||||
"emails_deleted": "{count, plural, other {# e-posta silindi}}",
|
||||
"emails_archived": "{count, plural, other {# e-posta arşivlendi}}",
|
||||
"error_archiving": "E-posta arşivlenemedi",
|
||||
"error_updating": "E-posta güncellenemedi",
|
||||
"email_starred": "E-posta yıldızlandı",
|
||||
"email_unstarred": "E-postanın yıldızı kaldırıldı",
|
||||
"email_marked_read": "E-posta okundu olarak işaretlendi",
|
||||
@@ -1143,7 +1163,9 @@
|
||||
"enabled": "E-posta bildirimleri",
|
||||
"enabled_desc": "Yeni e-postalar geldiğinde bildirim göster",
|
||||
"sound": "Bildirim sesi",
|
||||
"sound_desc": "Yeni e-postalar geldiğinde sesli uyarı çal"
|
||||
"sound_desc": "Yeni e-postalar geldiğinde sesli uyarı çal",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Takvim Bildirimleri",
|
||||
@@ -1172,6 +1194,7 @@
|
||||
"status_busy": "Çalışıyor…",
|
||||
"status_inactive": "Bu cihazda etkinleştirilmedi",
|
||||
"status_unsupported": "Bu tarayıcı Web Push'u desteklemiyor",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"title": "Arka plan bildirimleri",
|
||||
"devices_title": "Kayıtlı cihazlar",
|
||||
"devices_desc": "Bu hesap için arka plan bildirimleri almak üzere kayıtlı tüm cihazlar. Birini iptal ettiğinizde o cihaz bildirimleri hemen almayı bırakır.",
|
||||
@@ -1238,6 +1261,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Klasör değiştirirken aramayı temizle",
|
||||
"description": "Bir klasöre tıklamak (Gelen Kutusu, Gönderilenler…) etkin aramayı temizler ve o klasörü gösterir. Kapalıyken arama korunur ve geçtiğiniz klasörde yeniden çalıştırılır."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Varsayılan olarak okundu bilgisi iste",
|
||||
"description": "Yeni bir ileti yazarken okundu bilgisi isteğini önceden etkinleştir."
|
||||
@@ -1321,6 +1348,12 @@
|
||||
"label": "Yanıt Adresini Otomatik Seç",
|
||||
"description": "Yanıtlarken, Kimden adresini iletiyi başlangıçta alan kimliğe otomatik olarak değiştir"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Alıcı adresi eşleştirme",
|
||||
"description": "Hangi alıcı adreslerinin sizin sayılacağı. „Yalnızca tam adres“ sadece yapılandırılmış kimliklerinizden birini seçer; „aynı alan adı“ ayrıca kimlik alan adlarınızdaki diğer tüm adresleri catch-all takma adı olarak değerlendirir ve Kimden başlığını buna göre yeniden yazar. Bu adresler dağıtım listesiyse tam adresi seçin.",
|
||||
"exact": "Yalnızca tam adres",
|
||||
"domain": "Alan adlarımdaki herhangi bir adres"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "İmza konumu",
|
||||
"description": "Yanıtlarda ve iletmelerde imzanızın nereye ekleneceği. Alıntılanan metnin üzerinde, yanıt için doğal bir kapanış olarak okunur; altında ise orijinal mesajı bir bütün hâlinde tutar.",
|
||||
@@ -1746,7 +1779,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "Bitiş tarihi başlangıç tarihinden sonra olmalıdır",
|
||||
"start_in_past": "Başlangıç tarihi geçmişte",
|
||||
"empty_body": "İleti gövdesi boş - alıcılar boş bir yanıt alacak"
|
||||
"empty_body": "İleti gövdesi boş - alıcılar boş bir yanıt alacak",
|
||||
"subject_too_long": "Konu sunucu için çok uzun (en fazla {max} bayt).",
|
||||
"body_too_long": "İleti sunucu için çok uzun (biçimlendirme dahil en fazla {max} bayt)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1763,6 +1798,8 @@
|
||||
"role_trash": "Çöp Kutusu",
|
||||
"role_junk": "Spam / İstem Dışı",
|
||||
"role_archive": "Arşiv",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Yok",
|
||||
"create_folder": "Klasör Oluştur",
|
||||
"create_subfolder": "Alt Klasör Oluştur",
|
||||
@@ -1872,6 +1909,8 @@
|
||||
"title": "Kişiler",
|
||||
"description": "Kişilerinizi içe ve dışa aktarın",
|
||||
"group_by_letter_label": "İlk harfe göre grupla",
|
||||
"sort_by_last_name_label": "Soyada göre sırala",
|
||||
"sort_by_last_name_description": "Aile üyeleri bir arada görünsün diye kişi listesini soyada göre sıralar",
|
||||
"group_by_letter_description": "Kişi listesinde alfabetik bölüm başlıkları göster",
|
||||
"import_label": "Kişileri İçe Aktar",
|
||||
"import_description": "Kişileri bir vCard (.vcf) dosyasından içe aktarın",
|
||||
@@ -1963,6 +2002,7 @@
|
||||
"actions": "İşlemler",
|
||||
"add_action": "İşlem Ekle",
|
||||
"stop_processing": "Sonraki kuralları işlemeyi durdur",
|
||||
"include_spam": "İstenmeyen olarak işaretlenen iletileri de taşı",
|
||||
"attachment_type_placeholder": "örn. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Değer (birden fazla virgülle ayrılır)",
|
||||
"condition_fields": {
|
||||
@@ -2004,6 +2044,8 @@
|
||||
"copy_to_folder": "Klasör seçin",
|
||||
"forward_to": "E-posta adresine ilet",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "Bir kopyasını sakla",
|
||||
"forward_limit": "Bu sunucuda ileti başına yönlendirme sınırı: {count}. Fazla yönlendirmeler atlanır.",
|
||||
"reject_message": "Reddetme iletisi",
|
||||
"reject_placeholder": "E-postanız reddedildi",
|
||||
"label_name": "Etiket adı",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Yeni alt klasör...",
|
||||
"new_folder": "Yeni klasör...",
|
||||
"rename": "Yeniden adlandır...",
|
||||
"share": "Paylaş...",
|
||||
"import_email": ".eml veya .zip içe aktar...",
|
||||
"empty_folder": "Klasörü boşalt",
|
||||
"empty_folder_generic": "Klasörü boşalt",
|
||||
@@ -2236,7 +2279,10 @@
|
||||
"toast_error_rename": "Klasör yeniden adlandırılamadı",
|
||||
"toast_error_delete": "Klasör silinemedi",
|
||||
"toast_error_delete_has_children": "Klasörde alt klasörler var. Önce onları kaldırın.",
|
||||
"toast_error_delete_has_email": "Klasör boş değil. Önce boşaltın."
|
||||
"toast_error_delete_has_email": "Klasör boş değil. Önce boşaltın.",
|
||||
"delete_with_contents_title": "Klasör boş değil",
|
||||
"delete_with_contents_message": "\"{name}\" klasöründe hâlâ {count} ileti var. Klasör ve tüm iletileri silinsin mi? Bu işlem geri alınamaz.",
|
||||
"delete_with_contents_confirm": "Klasörü ve iletileri sil"
|
||||
},
|
||||
"shortcuts": {
|
||||
"title": "Klavye Kısayolları",
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Aramaya odaklan",
|
||||
"help": "Kısayolları göster",
|
||||
"refresh": "E-postaları yenile",
|
||||
"select_all": "Tümünü seç"
|
||||
"select_all": "Tümünü seç",
|
||||
"global_search": "Her yerde ara"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "İleti dizisini genişlet/daralt"
|
||||
@@ -2721,7 +2768,10 @@
|
||||
"any_month": "Herhangi bir ay",
|
||||
"has_email": "E-postası var",
|
||||
"has_phone": "Telefonu var",
|
||||
"has_photo": "Fotoğrafı var"
|
||||
"has_photo": "Fotoğrafı var",
|
||||
"sort_by": "Sıralama",
|
||||
"sort_first_name": "Ad",
|
||||
"sort_last_name": "Soyad"
|
||||
},
|
||||
"open_categories": "Kategorileri aç"
|
||||
},
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "Çoğalt",
|
||||
"today_header": "Bugün",
|
||||
"tomorrow_header": "Yarın",
|
||||
"agenda_show_earlier": "Önceki etkinlikleri göster",
|
||||
"agenda_loading": "Yükleniyor…",
|
||||
"agenda_range_start": "{date} tarihinden itibaren etkinlikler",
|
||||
"agenda_range_end": "{date} tarihine kadar etkinlikler",
|
||||
"export_ics": ".ics olarak dışa aktar",
|
||||
"copy_title": "Başlığı kopyala",
|
||||
"copy_link": "Toplantı bağlantısını kopyala",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "Renk"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Uygunluk",
|
||||
"free": "Uygun",
|
||||
"busy": "Meşgul",
|
||||
"tentative": "Geçici",
|
||||
"unknown": "Bu sunucuda kullanıcı değil",
|
||||
"checking": "Kontrol ediliyor…"
|
||||
},
|
||||
"title": "Katılımcılar",
|
||||
"add": "Katılımcı ekle",
|
||||
"organizer": "Organizatör",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Tüm etkinlikler",
|
||||
"cancel": "İptal",
|
||||
"save": "Kaydet",
|
||||
"delete": "Sil"
|
||||
"delete": "Sil",
|
||||
"rsvp_title": "Yinelenen etkinliğe yanıt ver",
|
||||
"rsvp_description": "Bu yinelenen bir etkinlik. Yanıtınız hangi etkinlikler için geçerli?",
|
||||
"respond": "Yanıtla"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Hatırlatıcı",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Aylık takvim görünümünde etkinlik saatlerini görüntüle",
|
||||
"show_week_numbers": "Hafta numaralarını göster",
|
||||
"show_week_numbers_desc": "Mini takvimde hafta numaralarını görüntüle",
|
||||
"calendar_free_scroll": "Serbest kaydırma",
|
||||
"calendar_free_scroll_desc": "Tek seferde bir dönem yerine aylar, haftalar ve günler arasında kesintisiz kaydırın",
|
||||
"enable_tasks": "Görevleri etkinleştir",
|
||||
"enable_tasks_desc": "Yapılacaklar listesini yönetmek için takvimde görev görünümü göster",
|
||||
"show_tasks_on_calendar": "Görevleri takvimde göster",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "1 saniyelik gecikme",
|
||||
"hover_preview_delay_2s": "2 saniyelik gecikme",
|
||||
"hover_preview_off": "Devre dışı",
|
||||
"organizer_identity": "Davetleri şu kimlikle düzenle",
|
||||
"organizer_identity_desc": "Kişileri etkinliklere davet ederken düzenleyici olarak kullanılan takvim adresi.",
|
||||
"organizer_identity_failed": "Düzenleyici adresi değiştirilemedi",
|
||||
"show_birthday_calendar": "Kişi doğum günü takvimi",
|
||||
"show_birthday_calendar_desc": "Kişilerinizdeki doğum günlerini gösteren sanal takvim göster"
|
||||
},
|
||||
@@ -2973,6 +3043,9 @@
|
||||
"alert_now": "Şimdi başlıyor",
|
||||
"alert_in_minutes": "{count} dk içinde",
|
||||
"invitation_sent": "Davetler gönderildi",
|
||||
"invitations_denied_title": "Davetler gönderilemiyor",
|
||||
"invitations_denied": "Sunucu davetleri göndermeyi reddetti: {reason}. Etkinlik davet gönderilmeden kaydedilsin mi?",
|
||||
"save_without_invitations": "Davetsiz kaydet",
|
||||
"rsvp_updated": "Yanıt güncellendi",
|
||||
"rsvp_error": "Yanıt güncellenemedi",
|
||||
"event_duplicated": "Etkinlik çoğaltıldı",
|
||||
@@ -3001,7 +3074,7 @@
|
||||
"supported_formats": "iCalendar (.ics) dosyalarını destekler",
|
||||
"url_description": "Etkinlikleri içe aktarmak için harici iCalendar (.ics) akışının URL'sini girin.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "CalDAV ve iCalendar (.ics) URL'lerini destekler",
|
||||
"url_hint": "iCalendar (.ics) URL'lerini destekler",
|
||||
"fetch": "Getir",
|
||||
"invalid_url": "Lütfen geçerli bir URL girin",
|
||||
"url_fetch_failed": "URL'den takvim getirilemedi",
|
||||
@@ -3149,6 +3222,25 @@
|
||||
},
|
||||
"nav_open_menu": "Menüyü aç"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Birisi",
|
||||
"invited": "{name} sizi \"{title}\" etkinliğine davet etti",
|
||||
"updated": "{name} \"{title}\" etkinliğini güncelledi",
|
||||
"cancelled": "{name} \"{title}\" etkinliğini iptal etti",
|
||||
"untitled": "Başlıksız etkinlik"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Birisi",
|
||||
"shared": "{name}, \"{object}\" {kind} sizinle paylaştı",
|
||||
"changed": "{name}, \"{object}\" {kind} erişiminizi değiştirdi",
|
||||
"revoked": "{name}, \"{object}\" {kind} erişiminizi kaldırdı",
|
||||
"object": {
|
||||
"folder": "klasörünü",
|
||||
"calendar": "takvimini",
|
||||
"address_book": "adres defterini",
|
||||
"files": "dosya klasörünü"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "\"{name}\" paylaş",
|
||||
"description": "Bu sunucudaki diğer kullanıcılara veya gruplara erişim verin. Değişiklikler hemen geçerli olur.",
|
||||
@@ -3187,6 +3279,8 @@
|
||||
"has_attachment": "Ekler",
|
||||
"date_after": "Sonra",
|
||||
"date_before": "Önce",
|
||||
"size_min": "Şundan büyük (KB)",
|
||||
"size_max": "Şundan küçük (KB)",
|
||||
"starred": "Yıldızlı",
|
||||
"unread": "Okunmamış",
|
||||
"read": "Okunmuş",
|
||||
@@ -3228,6 +3322,10 @@
|
||||
"hint": "Başlamak için soldaki herhangi bir e-postaya tıklayın veya aşağıdaki turu yapın."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Belgeyi düzenle",
|
||||
"office_loading": "Belge düzenleyici açılıyor…",
|
||||
"office_error": "Belge düzenleyici açılamadı.",
|
||||
"office_close": "Düzenleyiciyi kapat",
|
||||
"open_in_new_tab": "Yeni sekmede aç",
|
||||
"title": "Dosyalar",
|
||||
"search_placeholder": "Dosyalarda ara...",
|
||||
@@ -3240,6 +3338,8 @@
|
||||
"rename": "Yeniden Adlandır",
|
||||
"rename_title": "Yeniden Adlandır",
|
||||
"new_name": "Yeni ad",
|
||||
"name_forbidden_chars": "Sunucu adlarda şu karakterlere izin vermiyor: {chars}",
|
||||
"name_reserved": "Bu ad sunucuda ayrılmış.",
|
||||
"delete": "Sil",
|
||||
"delete_confirm_title": "Kaynağı sil",
|
||||
"delete_confirm_message": "\"{name}\" silinsin mi? Bu geri alınamaz.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Etkinlik bağlantısını kopyala",
|
||||
"copied": "Bağlantı kopyalandı",
|
||||
"copy_failed": "Bağlantı kopyalanamadı"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Yetkilendirme tamamlanıyor…",
|
||||
"completing_message": "Bağlantı kurulurken lütfen bekleyin.",
|
||||
"received_title": "Yetkilendirme alındı",
|
||||
"received_message": "Bağlantıyı başlattığınız sekmeye dönebilirsiniz.",
|
||||
"failed_title": "Yetkilendirme başarısız oldu",
|
||||
"denied": "Yetkilendirme reddedildi.",
|
||||
"failed_detail": "Yetkilendirme başarısız oldu: {error}",
|
||||
"missing_params": "Yetkilendirme kodu veya durum eksik.",
|
||||
"delivery_failed": "Yetkilendirme sonucu uygulama sekmesine iletilemedi. Lütfen bu pencereyi kapatıp tekrar deneyin."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Her yerde ara",
|
||||
"placeholder": "Posta, kişiler, takvim ve dosyalarda ara...",
|
||||
"scope_label": "Şuralarda ara",
|
||||
"account_label": "Hesap",
|
||||
"scope_all": "Tümü",
|
||||
"scope_mail": "Posta",
|
||||
"scope_contacts": "Kişiler",
|
||||
"scope_calendar": "Takvim",
|
||||
"scope_files": "Dosyalar",
|
||||
"all_accounts": "Tüm hesaplar",
|
||||
"show_all": "Tüm sonuçları göster",
|
||||
"no_results": "“{query}” için sonuç yok",
|
||||
"server_hint": "Tam kelimeler sunucuda arar; kelime parçaları yüklenmiş öğelerle eşleşir",
|
||||
"searching": "Aranıyor...",
|
||||
"account_error": "{account} içinde arama yapılamadı",
|
||||
"recent_searches": "Son aramalar",
|
||||
"recurring": "Yinelenen",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Tüm hesaplarda posta, kişi, etkinlik ve dosya arayın. Şunları deneyin: from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Tarihe göre düz",
|
||||
"grouped": "Gruplandırılmış",
|
||||
"load_more": "Daha fazla yükle",
|
||||
"preview_empty": "Önizlemek için bir sonuç seçin",
|
||||
"open_full": "Aç",
|
||||
"view_label": "Görünüm"
|
||||
}
|
||||
}
|
||||
|
||||
+153
-13
@@ -225,6 +225,10 @@
|
||||
"and_others": "та {count} інші",
|
||||
"draft": "Чернетка",
|
||||
"starred": "Позначено зірочкою",
|
||||
"pinned": "Закріплено",
|
||||
"replied": "Відповіли",
|
||||
"forwarded": "Переслано",
|
||||
"has_attachment": "З вкладенням",
|
||||
"conversations_count": "{count} з {total} розмов",
|
||||
"conversations_count_plus": "{count}+ розмов",
|
||||
"conversations_count_simple": "{count} розмов",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "вперед",
|
||||
"delete": "Видалити",
|
||||
"archive": "Архів",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "зірка",
|
||||
"unstar": "Зняти зірочку",
|
||||
"mark_unread": "Позначити як непрочитане",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "Перенести",
|
||||
"print": "Роздрукувати",
|
||||
"view_source": "Переглянути джерело",
|
||||
|
||||
"fullscreen": "На весь екран",
|
||||
|
||||
"exit_fullscreen": "Вийти з повноекранного режиму",
|
||||
"export_email": "Експортувати як .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "Переслати як вкладення",
|
||||
"import_email": "Імпорт .eml або .zip",
|
||||
"keyboard_shortcuts": "Комбінації клавіш (?)",
|
||||
"email_source": "Джерело електронної пошти",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "Запитано сповіщення про прочитання (натисніть, щоб вимкнути)",
|
||||
"read_receipt_off": "Запитати сповіщення про прочитання",
|
||||
"dsn_on": "Запитано сповіщення про доставку (натисніть, щоб вимкнути)",
|
||||
"dsn_off": "Запитати сповіщення про доставку",
|
||||
"format_plain_text": "Перемкнути на простий текст",
|
||||
"format_rich_text": "Перемкнути на форматований текст (HTML)",
|
||||
"require_tls_on": "Потрібна зашифрована доставка (натисніть, щоб вимкнути)",
|
||||
"require_tls_off": "Вимагати зашифровану доставку (TLS)",
|
||||
"new_message": "Нове повідомлення",
|
||||
"signature_edit_hint": "Двічі клацніть, щоб редагувати підпис",
|
||||
"reply": "Відповісти",
|
||||
@@ -631,11 +640,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "Замінити",
|
||||
"toggle_on": "Скасувати заміну",
|
||||
"toggle_tooltip": "Вільно редагуйте ім'я та адресу відправника. Пошта все ще надсилається через вашу ідентичність - змінюється лише видимий заголовок Від.",
|
||||
"toggle_tooltip": "Вільно редагуйте ім'я та адресу відправника. Пошта все ще надсилається через вашу ідентичність.",
|
||||
"name_label": "Ім'я відправника",
|
||||
"name_placeholder": "Ім'я",
|
||||
"email_label": "Електронна адреса відправника",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "Якщо ваш сервер не приймає цю адресу як відправника конверта, замість неї використовується {identity}, і одержувачі бачитимуть її в заголовку Return-Path."
|
||||
},
|
||||
"use_template": "Шаблон",
|
||||
"save_as_template": "Зберегти як шаблон",
|
||||
@@ -648,6 +658,8 @@
|
||||
"upload_progress": "Завантаження {uploaded} / {total}",
|
||||
"upload_cancel": "Скасувати завантаження",
|
||||
"upload_failed": "Не вдалося завантажити {filename}",
|
||||
"attachment_too_large": "«{name}» більший, ніж дозволяє сервер ({max} на файл)",
|
||||
"attachments_total_too_large": "Вкладення перевищать ліміт сервера {max} на лист",
|
||||
"drop_files": "Перетягніть файли, щоб прикріпити",
|
||||
"show_less": "Показувати менше",
|
||||
"send_failed": "Не вдалося надіслати електронний лист",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "Видалити стовпець",
|
||||
"toggle_header_row": "Переключити рядок заголовка",
|
||||
"delete_table": "Видалити таблицю",
|
||||
"pick_size": "Вибрати розмір"
|
||||
"pick_size": "Вибрати розмір",
|
||||
"font_size": "Розмір шрифту",
|
||||
"font_size_default": "За замовчуванням",
|
||||
"background_color": "Колір фону",
|
||||
"remove_background_color": "Прибрати колір фону"
|
||||
},
|
||||
"send_filing_warning": "Надіслано - але подальше очищення не вдалося, може залишитися застаріла чернетка."
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "Електронна пошта успішно надіслана",
|
||||
"email_deleted": "Електронну пошту видалено",
|
||||
"email_archived": "Електронний лист заархівовано",
|
||||
"emails_deleted": "{count, plural, one {# лист видалено} few {# листи видалено} many {# листів видалено} other {# листа видалено}}",
|
||||
"emails_archived": "{count, plural, one {# лист архівовано} few {# листи архівовано} many {# листів архівовано} other {# листа архівовано}}",
|
||||
"error_archiving": "Не вдалося архівувати лист",
|
||||
"error_updating": "Не вдалося оновити лист",
|
||||
"email_starred": "Електронна пошта позначена зірочкою",
|
||||
"email_unstarred": "З електронної пошти знято зірочку",
|
||||
"email_marked_read": "Електронна пошта позначена як прочитана",
|
||||
@@ -1143,7 +1163,9 @@
|
||||
"enabled": "Сповіщення електронною поштою",
|
||||
"enabled_desc": "Показувати сповіщення про надходження нових електронних листів",
|
||||
"sound": "Звук сповіщення",
|
||||
"sound_desc": "Відтворення звукового сповіщення про надходження нових електронних листів"
|
||||
"sound_desc": "Відтворення звукового сповіщення про надходження нових електронних листів",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "Сповіщення календаря",
|
||||
@@ -1172,6 +1194,7 @@
|
||||
"status_busy": "Виконується…",
|
||||
"status_inactive": "Не ввімкнено на цьому пристрої",
|
||||
"status_unsupported": "Цей браузер не підтримує Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"title": "Фонові сповіщення",
|
||||
"devices_title": "Зареєстровані пристрої",
|
||||
"devices_desc": "Усі пристрої, зареєстровані для отримання фонових сповіщень цього облікового запису. Після відкликання пристрій одразу перестає їх отримувати.",
|
||||
@@ -1238,6 +1261,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "Очищати пошук під час перемикання тек",
|
||||
"description": "Клацання теки («Вхідні», «Надіслані»…) очищає активний пошук і відкриває цю теку. Якщо вимкнено, пошук лишається й повторюється у вибраній теці."
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "Запитувати сповіщення про прочитання за замовчуванням",
|
||||
"description": "Заздалегідь вмикати запит сповіщення про прочитання під час створення нового повідомлення."
|
||||
@@ -1321,6 +1348,12 @@
|
||||
"label": "Автоматичний вибір адреси для відповіді",
|
||||
"description": "Під час відповіді автоматично змінюйте адресу відправника на особу, яка спочатку отримала повідомлення"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "Зіставлення адреси отримання",
|
||||
"description": "Які адреси отримання вважати вашими. «Лише точна адреса» вибирає виключно одну з налаштованих ідентичностей; «той самий домен» також вважає будь-яку іншу адресу на доменах ваших ідентичностей catch-all-псевдонімом і підставляє її в заголовок «Від». Виберіть точну адресу, якщо такі адреси є списками розсилки.",
|
||||
"exact": "Лише точна адреса",
|
||||
"domain": "Будь-яка адреса на моїх доменах"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "Розташування підпису",
|
||||
"description": "Куди вставляти підпис у відповідях і пересиланнях. Над цитованим текстом читається природно як завершення відповіді; під ним зберігає цілісність оригінального повідомлення.",
|
||||
@@ -1746,7 +1779,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "Дата завершення має бути після дати початку",
|
||||
"start_in_past": "Дата початку в минулому",
|
||||
"empty_body": "Тіло повідомлення порожнє - одержувачі отримають порожню відповідь"
|
||||
"empty_body": "Тіло повідомлення порожнє - одержувачі отримають порожню відповідь",
|
||||
"subject_too_long": "Тема задовга для сервера (не більше {max} байтів).",
|
||||
"body_too_long": "Повідомлення задовге для сервера (не більше {max} байтів разом із форматуванням)."
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1763,6 +1798,8 @@
|
||||
"role_trash": "сміття",
|
||||
"role_junk": "Спам/сміття",
|
||||
"role_archive": "Архів",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "Жодного",
|
||||
"create_folder": "Створити папку",
|
||||
"create_subfolder": "Створити вкладену папку",
|
||||
@@ -1882,7 +1919,9 @@
|
||||
"categories_description": "Перейменувати категорії контактів",
|
||||
"no_categories": "Категорії не знайдено",
|
||||
"group_by_letter_description": "Показувати алфавітні заголовки розділів у списку контактів",
|
||||
"group_by_letter_label": "Групувати за першою літерою"
|
||||
"group_by_letter_label": "Групувати за першою літерою",
|
||||
"sort_by_last_name_label": "Сортувати за прізвищем",
|
||||
"sort_by_last_name_description": "Упорядкувати список контактів за прізвищем, щоб члени родини були поруч"
|
||||
},
|
||||
"downloads": {
|
||||
"title": "Завантаження",
|
||||
@@ -1963,6 +2002,7 @@
|
||||
"actions": "Дії",
|
||||
"add_action": "Додати дію",
|
||||
"stop_processing": "Зупинити обробку наступних правил",
|
||||
"include_spam": "Переміщувати також листи, позначені як спам",
|
||||
"attachment_type_placeholder": "напр. pdf, doc, jpg",
|
||||
"value_placeholder_multi": "Значення (декілька через кому)",
|
||||
"condition_fields": {
|
||||
@@ -2004,6 +2044,8 @@
|
||||
"copy_to_folder": "Виберіть папку",
|
||||
"forward_to": "Переслати на електронну адресу",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "Зберегти копію",
|
||||
"forward_limit": "Ліміт пересилань на один лист на цьому сервері: {count}. Зайві пересилання пропускаються.",
|
||||
"reject_message": "Повідомлення про відмову",
|
||||
"reject_placeholder": "Ваш лист було відхилено",
|
||||
"label_name": "Назва мітки",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "Нова вкладена папка...",
|
||||
"new_folder": "Нова папка...",
|
||||
"rename": "Перейменувати...",
|
||||
"share": "Поділитися...",
|
||||
"import_email": "Імпортувати .eml або .zip...",
|
||||
"empty_folder": "Очистити папку",
|
||||
"empty_folder_generic": "Очистити папку",
|
||||
@@ -2234,6 +2277,9 @@
|
||||
"toast_error_delete": "Не вдалося видалити папку",
|
||||
"toast_error_delete_has_children": "Папка містить вкладені папки. Спочатку видаліть їх.",
|
||||
"toast_error_delete_has_email": "Папка не порожня. Спочатку очистіть її.",
|
||||
"delete_with_contents_title": "Тека не порожня",
|
||||
"delete_with_contents_message": "Тека «{name}» досі містить {count, plural, one {# повідомлення} few {# повідомлення} other {# повідомлень}}. Видалити теку разом з усіма повідомленнями? Цю дію не можна скасувати.",
|
||||
"delete_with_contents_confirm": "Видалити теку та повідомлення",
|
||||
"placeholder_folder_name": "Ім'я папки",
|
||||
"create": "Створити",
|
||||
"rename_confirm": "Перейменувати"
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "Пошук по фокусу",
|
||||
"help": "Показати ярлики",
|
||||
"refresh": "Оновити електронні листи",
|
||||
"select_all": "Вибрати все"
|
||||
"select_all": "Вибрати все",
|
||||
"global_search": "Шукати скрізь"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "Розгорнути/згорнути ланцюжок"
|
||||
@@ -2721,7 +2768,10 @@
|
||||
"any_month": "Будь-який місяць",
|
||||
"has_email": "З ел. поштою",
|
||||
"has_phone": "З телефоном",
|
||||
"has_photo": "З фото"
|
||||
"has_photo": "З фото",
|
||||
"sort_by": "Сортувати за",
|
||||
"sort_first_name": "Ім'я",
|
||||
"sort_last_name": "Прізвище"
|
||||
},
|
||||
"open_categories": "Відкрити категорії"
|
||||
},
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "дублікат",
|
||||
"today_header": "Сьогодні",
|
||||
"tomorrow_header": "завтра",
|
||||
"agenda_show_earlier": "Показати попередні події",
|
||||
"agenda_loading": "Завантаження…",
|
||||
"agenda_range_start": "Події з {date}",
|
||||
"agenda_range_end": "Події до {date}",
|
||||
"export_ics": "Експортувати як .ics",
|
||||
"copy_title": "Скопіювати назву",
|
||||
"copy_link": "Скопіювати посилання зустрічі",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "колір"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "Доступність",
|
||||
"free": "Вільний",
|
||||
"busy": "Зайнятий",
|
||||
"tentative": "Попередньо",
|
||||
"unknown": "Не користувач цього сервера",
|
||||
"checking": "Перевірка…"
|
||||
},
|
||||
"title": "Учасники",
|
||||
"add": "Додати учасника",
|
||||
"organizer": "Організатор",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "Всі події",
|
||||
"cancel": "Скасувати",
|
||||
"save": "зберегти",
|
||||
"delete": "Видалити"
|
||||
"delete": "Видалити",
|
||||
"rsvp_title": "Відповісти на повторювану подію",
|
||||
"rsvp_description": "Це повторювана подія. До яких подій стосується ваша відповідь?",
|
||||
"respond": "Відповісти"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "Нагадування",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "Відображення часу подій у місячному календарі",
|
||||
"show_week_numbers": "Показати номери тижнів",
|
||||
"show_week_numbers_desc": "Відображення номерів тижнів у міні-календарі",
|
||||
"calendar_free_scroll": "Вільне прокручування",
|
||||
"calendar_free_scroll_desc": "Безперервно прокручувати місяці, тижні та дні замість одного періоду за раз",
|
||||
"enable_tasks": "Увімкнути завдання",
|
||||
"enable_tasks_desc": "Показати перегляд завдань у календарі для керування справами",
|
||||
"show_tasks_on_calendar": "Показати завдання в календарі",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "1-секундна затримка",
|
||||
"hover_preview_delay_2s": "2-секундна затримка",
|
||||
"hover_preview_off": "Вимкнено",
|
||||
"organizer_identity": "Організовувати запрошення як",
|
||||
"organizer_identity_desc": "Адреса календаря, що використовується як організатор, коли ви запрошуєте людей на події.",
|
||||
"organizer_identity_failed": "Не вдалося змінити адресу організатора",
|
||||
"show_birthday_calendar": "Контактний календар днів народження",
|
||||
"show_birthday_calendar_desc": "Показати віртуальний календар із днями народження ваших контактів"
|
||||
},
|
||||
@@ -2947,6 +3017,9 @@
|
||||
"alert_now": "Починаючи зараз",
|
||||
"alert_in_minutes": "Через {count} хв",
|
||||
"invitation_sent": "Запрошення надіслано",
|
||||
"invitations_denied_title": "Не вдається надіслати запрошення",
|
||||
"invitations_denied": "Сервер відмовився надіслати запрошення: {reason}. Зберегти подію без їх надсилання?",
|
||||
"save_without_invitations": "Зберегти без запрошень",
|
||||
"rsvp_updated": "Відповідь оновлено",
|
||||
"rsvp_error": "Не вдалося оновити відповідь",
|
||||
"event_duplicated": "Подія дублюється",
|
||||
@@ -2975,7 +3048,7 @@
|
||||
"supported_formats": "Підтримує файли iCalendar (.ics).",
|
||||
"url_description": "Введіть URL-адресу зовнішнього каналу iCalendar (.ics), щоб імпортувати події.",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "Підтримує URL-адреси CalDAV та iCalendar (.ics).",
|
||||
"url_hint": "Підтримує URL-адреси iCalendar (.ics).",
|
||||
"fetch": "Принести",
|
||||
"invalid_url": "Введіть дійсну URL-адресу",
|
||||
"url_fetch_failed": "Не вдалося отримати календар з URL-адреси",
|
||||
@@ -3164,6 +3237,8 @@
|
||||
"has_attachment": "Додатки",
|
||||
"date_after": "Після",
|
||||
"date_before": "Раніше",
|
||||
"size_min": "Більше ніж (КБ)",
|
||||
"size_max": "Менше ніж (КБ)",
|
||||
"starred": "Позначено зірочкою",
|
||||
"unread": "Непрочитаний",
|
||||
"read": "Прочитайте",
|
||||
@@ -3205,6 +3280,10 @@
|
||||
"hint": "Натисніть будь-яку електронну пошту ліворуч, щоб розпочати, або перегляньте огляд нижче."
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "Редагувати документ",
|
||||
"office_loading": "Відкриття редактора документів…",
|
||||
"office_error": "Не вдалося відкрити редактор документів.",
|
||||
"office_close": "Закрити редактор",
|
||||
"open_in_new_tab": "Відкрити в новій вкладці",
|
||||
"title": "Файли",
|
||||
"search_placeholder": "Пошук файлів...",
|
||||
@@ -3217,6 +3296,8 @@
|
||||
"rename": "Перейменувати",
|
||||
"rename_title": "Перейменувати",
|
||||
"new_name": "Нова назва",
|
||||
"name_forbidden_chars": "Сервер не дозволяє ці символи в назвах: {chars}",
|
||||
"name_reserved": "Ця назва зарезервована на сервері.",
|
||||
"delete": "Видалити",
|
||||
"delete_confirm_title": "Видалити ресурс",
|
||||
"delete_confirm_message": "Ви впевнені, що хочете видалити \"{name}\"? Це неможливо скасувати.",
|
||||
@@ -3451,6 +3532,25 @@
|
||||
"unified_mailbox": {
|
||||
"search_unavailable": "Пошук недоступний в об'єднаному перегляді"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "Хтось",
|
||||
"invited": "{name} запросив(ла) вас на «{title}»",
|
||||
"updated": "{name} оновив(ла) «{title}»",
|
||||
"cancelled": "{name} скасував(ла) «{title}»",
|
||||
"untitled": "Подія без назви"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "Хтось",
|
||||
"shared": "{name} поділився(лася) з вами {kind} «{object}»",
|
||||
"changed": "{name} змінив(ла) ваш доступ до {kind} «{object}»",
|
||||
"revoked": "{name} скасував(ла) ваш доступ до {kind} «{object}»",
|
||||
"object": {
|
||||
"folder": "текою",
|
||||
"calendar": "календарем",
|
||||
"address_book": "адресною книгою",
|
||||
"files": "текою файлів"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "Поділитися «{name}»",
|
||||
"description": "Надайте доступ іншим користувачам або групам на цьому сервері. Зміни набувають чинності негайно.",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "Скопіювати посилання на подію",
|
||||
"copied": "Посилання скопійовано",
|
||||
"copy_failed": "Не вдалося скопіювати посилання"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "Завершення авторизації…",
|
||||
"completing_message": "Зачекайте, поки встановлюється з'єднання.",
|
||||
"received_title": "Авторизацію отримано",
|
||||
"received_message": "Ви можете повернутися на вкладку, де розпочали підключення.",
|
||||
"failed_title": "Не вдалося виконати авторизацію",
|
||||
"denied": "В авторизації відмовлено.",
|
||||
"failed_detail": "Не вдалося виконати авторизацію: {error}",
|
||||
"missing_params": "Відсутній код авторизації або стан.",
|
||||
"delivery_failed": "Не вдалося передати результат авторизації на вкладку програми. Закрийте це вікно та спробуйте ще раз."
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "Шукати скрізь",
|
||||
"placeholder": "Пошук у пошті, контактах, календарі, файлах...",
|
||||
"scope_label": "Шукати в",
|
||||
"account_label": "Обліковий запис",
|
||||
"scope_all": "Усе",
|
||||
"scope_mail": "Пошта",
|
||||
"scope_contacts": "Контакти",
|
||||
"scope_calendar": "Календар",
|
||||
"scope_files": "Файли",
|
||||
"all_accounts": "Усі облікові записи",
|
||||
"show_all": "Показати всі результати",
|
||||
"no_results": "Нічого не знайдено за запитом «{query}»",
|
||||
"server_hint": "Цілі слова шукаються на сервері; частини слів — серед уже завантаженого",
|
||||
"searching": "Пошук...",
|
||||
"account_error": "Не вдалося виконати пошук у {account}",
|
||||
"recent_searches": "Останні запити",
|
||||
"recurring": "Повторювана",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "Шукайте пошту, контакти, події та файли в усіх облікових записах. Спробуйте from:, subject:, has:attachment, after:2026-01-01.",
|
||||
"flat_by_date": "Списком за датою",
|
||||
"grouped": "За групами",
|
||||
"load_more": "Завантажити ще",
|
||||
"preview_empty": "Виберіть результат для перегляду",
|
||||
"open_full": "Відкрити",
|
||||
"view_label": "Вигляд"
|
||||
}
|
||||
}
|
||||
|
||||
+158
-11
@@ -202,6 +202,9 @@
|
||||
"delete_confirm": "您確定要刪除「{name}」嗎?",
|
||||
"no_apps": "尚未新增任何應用程式",
|
||||
"no_apps_hint": "在側邊欄新增自訂應用程式與連結",
|
||||
"managed_title": "由您的管理員提供",
|
||||
"managed_description": "這些應用程式為所有人統一設定,無法變更。",
|
||||
"managed_badge": "受管理",
|
||||
"search_icons": "搜尋圖示…",
|
||||
"show_popular": "熱門",
|
||||
"show_all": "全部",
|
||||
@@ -222,6 +225,10 @@
|
||||
"and_others": "另有 {count} 人",
|
||||
"draft": "草稿",
|
||||
"starred": "已加星號",
|
||||
"pinned": "已釘選",
|
||||
"replied": "已回覆",
|
||||
"forwarded": "已轉寄",
|
||||
"has_attachment": "有附件",
|
||||
"conversations_count": "顯示 {count} 個對話串,共 {total} 個",
|
||||
"conversations_count_plus": "{count}+ 個對話串",
|
||||
"conversations_count_simple": "{count} 個對話串",
|
||||
@@ -289,6 +296,7 @@
|
||||
"forward": "轉寄",
|
||||
"delete": "刪除",
|
||||
"archive": "封存",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "加上星號",
|
||||
"unstar": "移除星號",
|
||||
"mark_unread": "標示為未讀",
|
||||
@@ -303,7 +311,7 @@
|
||||
"fullscreen": "全螢幕",
|
||||
"exit_fullscreen": "離開全螢幕",
|
||||
"export_email": "匯出為 .eml",
|
||||
"forward_as_attachment": "以附件轉寄",
|
||||
"forward_as_attachment": "以附件形式轉寄",
|
||||
"import_email": "匯入 .eml 或 .zip",
|
||||
"keyboard_shortcuts": "鍵盤快速鍵 (?)",
|
||||
"email_source": "郵件原始碼",
|
||||
@@ -575,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "已要求傳送已讀回執(點擊以停用)",
|
||||
"read_receipt_off": "請求已讀回執",
|
||||
"dsn_on": "已要求遞送通知(點擊停用)",
|
||||
"dsn_off": "要求遞送通知",
|
||||
"format_plain_text": "切換為純文字",
|
||||
"format_rich_text": "切換為 HTML 格式文字",
|
||||
"require_tls_on": "已要求加密遞送(點擊停用)",
|
||||
"require_tls_off": "要求加密遞送 (TLS)",
|
||||
"new_message": "新郵件",
|
||||
"signature_edit_hint": "點兩下即可編輯簽名檔",
|
||||
"reply": "回覆",
|
||||
@@ -629,11 +643,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "覆寫",
|
||||
"toggle_on": "取消覆寫",
|
||||
"toggle_tooltip": "自由編輯寄件者名稱與電子郵件地址。郵件仍會透過您的寄件身分傳送,只有顯示的 From 標頭會變更。",
|
||||
"toggle_tooltip": "自由編輯寄件者名稱與電子郵件地址。郵件仍會透過您的寄件身分傳送。",
|
||||
"name_label": "寄件者姓名",
|
||||
"name_placeholder": "姓名",
|
||||
"email_label": "寄件者電子郵件地址",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "如果您的伺服器不接受此地址作為信封寄件者,將改用 {identity},收件者可以在 Return-Path 標頭中看到它。"
|
||||
},
|
||||
"use_template": "範本",
|
||||
"save_as_template": "儲存為範本",
|
||||
@@ -646,6 +661,8 @@
|
||||
"upload_progress": "正在上傳 {uploaded} / {total}",
|
||||
"upload_cancel": "取消上傳",
|
||||
"upload_failed": "無法上傳 {filename}",
|
||||
"attachment_too_large": "「{name}」超過伺服器允許的大小(每個檔案 {max})",
|
||||
"attachments_total_too_large": "附件將超過伺服器每封郵件 {max} 的上限",
|
||||
"drop_files": "拖放要附加的檔案",
|
||||
"show_less": "顯示較少",
|
||||
"send_failed": "傳送郵件失敗",
|
||||
@@ -721,7 +738,11 @@
|
||||
"delete_column": "刪除欄",
|
||||
"toggle_header_row": "切換標題列",
|
||||
"delete_table": "刪除表格",
|
||||
"pick_size": "選取大小"
|
||||
"pick_size": "選取大小",
|
||||
"font_size": "字型大小",
|
||||
"font_size_default": "預設",
|
||||
"background_color": "背景顏色",
|
||||
"remove_background_color": "移除背景顏色"
|
||||
},
|
||||
"send_filing_warning": "郵件已傳送,但寄送後清理失敗,可能仍保留舊草稿。"
|
||||
},
|
||||
@@ -760,6 +781,10 @@
|
||||
"email_sent": "郵件傳送成功",
|
||||
"email_deleted": "郵件已刪除",
|
||||
"email_archived": "郵件已封存",
|
||||
"emails_deleted": "{count, plural, other {已刪除 # 封郵件}}",
|
||||
"emails_archived": "{count, plural, other {已封存 # 封郵件}}",
|
||||
"error_archiving": "封存郵件失敗",
|
||||
"error_updating": "更新郵件失敗",
|
||||
"email_starred": "郵件已加上星號",
|
||||
"email_unstarred": "郵件已移除星號",
|
||||
"email_marked_read": "郵件已標示為已讀",
|
||||
@@ -1139,6 +1164,7 @@
|
||||
"status_active": "已在此裝置上啟用",
|
||||
"status_inactive": "在此裝置上未啟用",
|
||||
"status_unsupported": "此瀏覽器不支援 Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"status_busy": "工作中…",
|
||||
"enable": "啟用",
|
||||
"reenable": "重新註冊",
|
||||
@@ -1176,7 +1202,9 @@
|
||||
"enabled": "郵件通知",
|
||||
"enabled_desc": "新郵件到達時顯示通知",
|
||||
"sound": "通知聲音",
|
||||
"sound_desc": "新郵件到達時播放音訊提醒"
|
||||
"sound_desc": "新郵件到達時播放音訊提醒",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "行事曆通知",
|
||||
@@ -1236,6 +1264,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "切換資料夾時清除搜尋",
|
||||
"description": "點擊資料夾(收件匣、寄件備份等)會清除目前的搜尋並瀏覽該資料夾。關閉時,搜尋會保留並在切換到的資料夾中重新執行。"
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "預設請求已讀回執",
|
||||
"description": "撰寫新郵件時預先啟用已讀回執請求。"
|
||||
@@ -1319,6 +1351,12 @@
|
||||
"label": "使用收件地址回覆",
|
||||
"description": "回覆時,使用原始郵件的收件地址寄送。系統會先比對寄件身分;若是網域 catch-all 收件,則透過主要寄件身分傳送,但將 From 標頭改寫為別名地址。"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "接收地址比對",
|
||||
"description": "哪些接收地址算作您的地址。「僅精確地址」只從已設定的身分中選擇;「相同網域」還會將身分網域下的其他任何地址視為 catch-all 別名,並將寄件者標頭改寫為該地址。如果這些地址是通訊群組清單,請選擇精確地址。",
|
||||
"exact": "僅精確地址",
|
||||
"domain": "我的網域下的任意地址"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "簽名檔位置",
|
||||
"description": "在回覆與轉寄中插入簽名檔的位置。放在引用文字上方可自然作為回覆結尾;放在下方則能保持原始郵件連續。",
|
||||
@@ -1748,7 +1786,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "結束日期必須晚於開始日期",
|
||||
"start_in_past": "開始日期已在過去",
|
||||
"empty_body": "郵件內文為空 — 收件者將收到空白回覆"
|
||||
"empty_body": "郵件內文為空 — 收件者將收到空白回覆",
|
||||
"subject_too_long": "主旨對伺服器來說太長(最多 {max} 位元組)。",
|
||||
"body_too_long": "訊息對伺服器來說太長(含格式最多 {max} 位元組)。"
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1765,6 +1805,8 @@
|
||||
"role_trash": "垃圾桶",
|
||||
"role_junk": "垃圾郵件",
|
||||
"role_archive": "封存",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "無",
|
||||
"create_folder": "建立信件匣",
|
||||
"create_subfolder": "建立子信件匣",
|
||||
@@ -1865,6 +1907,8 @@
|
||||
"description": "管理側邊欄中的自訂應用程式與連結",
|
||||
"keep_loaded": "保持應用程式載入",
|
||||
"keep_loaded_description": "切換內嵌應用程式時,讓其在背景繼續執行,以避免重新載入",
|
||||
"managed_title": "由您的管理員提供",
|
||||
"managed_description": "這些應用程式為所有人統一設定,無法變更。",
|
||||
"manage_title": "自訂應用程式",
|
||||
"manage_description": "在側邊欄新增、編輯或移除自訂應用程式"
|
||||
},
|
||||
@@ -1872,6 +1916,8 @@
|
||||
"title": "聯絡人",
|
||||
"description": "匯入與匯出聯絡人",
|
||||
"group_by_letter_label": "依首字分組",
|
||||
"sort_by_last_name_label": "依姓氏排序",
|
||||
"sort_by_last_name_description": "依姓氏排列聯絡人清單,讓家庭成員顯示在一起",
|
||||
"group_by_letter_description": "在聯絡人清單中顯示依字母排列的分節標題",
|
||||
"import_label": "匯入聯絡人",
|
||||
"import_description": "從 vCard(.vcf)檔案匯入聯絡人",
|
||||
@@ -1963,6 +2009,7 @@
|
||||
"actions": "動作",
|
||||
"add_action": "新增動作",
|
||||
"stop_processing": "停止處理後續規則",
|
||||
"include_spam": "同時移動被標記為垃圾郵件的郵件",
|
||||
"attachment_type_placeholder": "例如:pdf、doc、jpg",
|
||||
"value_placeholder_multi": "值(多個用逗號分隔)",
|
||||
"condition_fields": {
|
||||
@@ -2004,6 +2051,8 @@
|
||||
"copy_to_folder": "選取信件匣",
|
||||
"forward_to": "轉寄至電子郵件地址",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "保留副本",
|
||||
"forward_limit": "此伺服器每封郵件的轉寄上限:{count}。多餘的轉寄將被略過。",
|
||||
"reject_message": "拒絕留言",
|
||||
"reject_placeholder": "您的郵件已被拒絕",
|
||||
"label_name": "標籤名稱",
|
||||
@@ -2200,6 +2249,7 @@
|
||||
"new_subfolder": "新增子信件匣…",
|
||||
"new_folder": "新增信件匣…",
|
||||
"rename": "重新命名…",
|
||||
"share": "共用...",
|
||||
"import_email": "匯入 .eml 或 .zip…",
|
||||
"empty_folder": "清空信件匣",
|
||||
"empty_folder_generic": "清空信件匣",
|
||||
@@ -2229,7 +2279,10 @@
|
||||
"toast_error_rename": "無法重新命名信件匣",
|
||||
"toast_error_delete": "無法刪除信件匣",
|
||||
"toast_error_delete_has_children": "信件匣內有子信件匣,請先移除。",
|
||||
"toast_error_delete_has_email": "信件匣不是空的,請先清空。"
|
||||
"toast_error_delete_has_email": "信件匣不是空的,請先清空。",
|
||||
"delete_with_contents_title": "資料夾不是空的",
|
||||
"delete_with_contents_message": "資料夾「{name}」中仍有 {count} 封郵件。要刪除該資料夾及其所有郵件嗎?此操作無法復原。",
|
||||
"delete_with_contents_confirm": "刪除資料夾和郵件"
|
||||
},
|
||||
"shortcuts": {
|
||||
"title": "鍵盤快速鍵",
|
||||
@@ -2263,7 +2316,8 @@
|
||||
"search": "將焦點移至搜尋框",
|
||||
"help": "顯示快速鍵",
|
||||
"refresh": "重新整理郵件",
|
||||
"select_all": "全部選取"
|
||||
"select_all": "全部選取",
|
||||
"global_search": "全域搜尋"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "展開/收合對話串"
|
||||
@@ -2715,7 +2769,10 @@
|
||||
"any_month": "任何月份",
|
||||
"has_email": "有電子郵件地址",
|
||||
"has_phone": "有電話",
|
||||
"has_photo": "有照片"
|
||||
"has_photo": "有照片",
|
||||
"sort_by": "排序方式",
|
||||
"sort_first_name": "名",
|
||||
"sort_last_name": "姓"
|
||||
}
|
||||
},
|
||||
"calendar": {
|
||||
@@ -2751,6 +2808,10 @@
|
||||
"duplicate": "建立副本",
|
||||
"today_header": "今天",
|
||||
"tomorrow_header": "明天",
|
||||
"agenda_show_earlier": "顯示更早的行程",
|
||||
"agenda_loading": "載入中…",
|
||||
"agenda_range_start": "顯示 {date} 起的行程",
|
||||
"agenda_range_end": "顯示至 {date} 的行程",
|
||||
"export_ics": "匯出為 .ics",
|
||||
"copy_title": "複製標題",
|
||||
"copy_link": "複製會議連結",
|
||||
@@ -2786,6 +2847,14 @@
|
||||
"color": "顏色"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "空閒狀態",
|
||||
"free": "有空",
|
||||
"busy": "忙碌",
|
||||
"tentative": "暫定",
|
||||
"unknown": "不是此伺服器上的使用者",
|
||||
"checking": "檢查中…"
|
||||
},
|
||||
"title": "參與者",
|
||||
"add": "新增參與者",
|
||||
"organizer": "主辦人",
|
||||
@@ -2860,7 +2929,10 @@
|
||||
"all_events": "所有活動",
|
||||
"cancel": "取消",
|
||||
"save": "儲存",
|
||||
"delete": "刪除"
|
||||
"delete": "刪除",
|
||||
"rsvp_title": "回覆重複活動",
|
||||
"rsvp_description": "這是重複活動。您的回覆適用於哪些活動?",
|
||||
"respond": "回覆"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "提醒",
|
||||
@@ -2898,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "在月檢視中顯示活動時間。小螢幕會顯示完整活動項目,而不只是圓點。",
|
||||
"show_week_numbers": "顯示週數",
|
||||
"show_week_numbers_desc": "在迷你行事曆中顯示週數",
|
||||
"calendar_free_scroll": "自由捲動",
|
||||
"calendar_free_scroll_desc": "連續捲動瀏覽月、週和日,而不是一次只顯示一個時段",
|
||||
"enable_tasks": "啟用任務",
|
||||
"enable_tasks_desc": "在行事曆中顯示任務檢視以管理待辦事項",
|
||||
"show_tasks_on_calendar": "在行事曆上顯示任務",
|
||||
@@ -2909,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "延遲 1 秒",
|
||||
"hover_preview_delay_2s": "延遲 2 秒",
|
||||
"hover_preview_off": "停用",
|
||||
"organizer_identity": "以此身分發送邀請",
|
||||
"organizer_identity_desc": "邀請他人參加活動時用作主辦者的行事曆地址。",
|
||||
"organizer_identity_failed": "無法變更主辦者地址",
|
||||
"show_birthday_calendar": "聯絡人生日行事曆",
|
||||
"show_birthday_calendar_desc": "顯示包含聯絡人生日的虛擬行事曆"
|
||||
},
|
||||
@@ -2966,6 +3043,9 @@
|
||||
"alert_now": "現在開始",
|
||||
"alert_in_minutes": "{count} 分鐘後",
|
||||
"invitation_sent": "已傳送邀請",
|
||||
"invitations_denied_title": "無法傳送邀請",
|
||||
"invitations_denied": "伺服器拒絕傳送邀請:{reason}。是否不傳送邀請直接儲存活動?",
|
||||
"save_without_invitations": "不傳送邀請並儲存",
|
||||
"rsvp_updated": "回覆已更新",
|
||||
"rsvp_error": "無法更新回覆",
|
||||
"event_duplicated": "已建立活動副本",
|
||||
@@ -2995,7 +3075,7 @@
|
||||
"supported_formats": "支援 iCalendar (.ics) 檔案",
|
||||
"url_description": "輸入外部 iCalendar(.ics)資料來源的 URL,以匯入活動。",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "支援 CalDAV 與 iCalendar(.ics)URL",
|
||||
"url_hint": "支援 iCalendar(.ics)URL",
|
||||
"fetch": "取得",
|
||||
"invalid_url": "請輸入有效的 URL",
|
||||
"url_fetch_failed": "無法從 URL 取得行事曆",
|
||||
@@ -3142,6 +3222,25 @@
|
||||
"overdue": "逾期"
|
||||
}
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "有人",
|
||||
"invited": "{name} 邀請您參加「{title}」",
|
||||
"updated": "{name} 更新了「{title}」",
|
||||
"cancelled": "{name} 取消了「{title}」",
|
||||
"untitled": "未命名活動"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "有人",
|
||||
"shared": "{name} 與您共用了{kind}「{object}」",
|
||||
"changed": "{name} 變更了您對{kind}「{object}」的存取權限",
|
||||
"revoked": "{name} 移除了您對{kind}「{object}」的存取權限",
|
||||
"object": {
|
||||
"folder": "資料夾",
|
||||
"calendar": "行事曆",
|
||||
"address_book": "通訊錄",
|
||||
"files": "檔案資料夾"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "分享「{name}」",
|
||||
"description": "授予此伺服器上的其他使用者或群組存取權。變更會立即生效。",
|
||||
@@ -3180,6 +3279,8 @@
|
||||
"has_attachment": "附件",
|
||||
"date_after": "後",
|
||||
"date_before": "前",
|
||||
"size_min": "大於 (KB)",
|
||||
"size_max": "小於 (KB)",
|
||||
"starred": "已加星號",
|
||||
"unread": "未讀",
|
||||
"read": "已讀",
|
||||
@@ -3221,6 +3322,10 @@
|
||||
"hint": "點擊左側任一郵件即可開始,或進行下方的功能導覽。"
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "編輯文件",
|
||||
"office_loading": "正在開啟文件編輯器…",
|
||||
"office_error": "無法開啟文件編輯器。",
|
||||
"office_close": "關閉編輯器",
|
||||
"open_in_new_tab": "在新分頁開啟",
|
||||
"title": "檔案",
|
||||
"search_placeholder": "搜尋檔案…",
|
||||
@@ -3233,6 +3338,8 @@
|
||||
"rename": "重新命名",
|
||||
"rename_title": "重新命名",
|
||||
"new_name": "新名稱",
|
||||
"name_forbidden_chars": "伺服器不允許名稱中包含這些字元:{chars}",
|
||||
"name_reserved": "此名稱已被伺服器保留。",
|
||||
"delete": "刪除",
|
||||
"delete_confirm_title": "刪除項目",
|
||||
"delete_confirm_message": "您確定要刪除「{name}」嗎?此動作無法復原。",
|
||||
@@ -3494,5 +3601,45 @@
|
||||
"copy_event": "複製活動連結",
|
||||
"copied": "連結已複製",
|
||||
"copy_failed": "無法複製連結"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "正在完成授權…",
|
||||
"completing_message": "請稍候,正在建立連線。",
|
||||
"received_title": "已收到授權",
|
||||
"received_message": "您可以返回開始連線的分頁。",
|
||||
"failed_title": "授權失敗",
|
||||
"denied": "授權被拒絕。",
|
||||
"failed_detail": "授權失敗:{error}",
|
||||
"missing_params": "缺少授權碼或狀態。",
|
||||
"delivery_failed": "無法將授權結果傳遞至應用程式分頁。請關閉此視窗並再試一次。"
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "全域搜尋",
|
||||
"placeholder": "搜尋郵件、聯絡人、行事曆、檔案...",
|
||||
"scope_label": "搜尋範圍",
|
||||
"account_label": "帳戶",
|
||||
"scope_all": "全部",
|
||||
"scope_mail": "郵件",
|
||||
"scope_contacts": "聯絡人",
|
||||
"scope_calendar": "行事曆",
|
||||
"scope_files": "檔案",
|
||||
"all_accounts": "所有帳戶",
|
||||
"show_all": "顯示所有結果",
|
||||
"no_results": "找不到「{query}」的結果",
|
||||
"server_hint": "完整單字會搜尋伺服器;部分單字則比對已載入的項目",
|
||||
"searching": "搜尋中...",
|
||||
"account_error": "無法搜尋 {account}",
|
||||
"recent_searches": "最近搜尋",
|
||||
"recurring": "重複",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "在所有帳戶中搜尋郵件、聯絡人、行事和檔案。可試試 from:、subject:、has:attachment、after:2026-01-01。",
|
||||
"flat_by_date": "依日期平列",
|
||||
"grouped": "分組",
|
||||
"load_more": "載入更多",
|
||||
"preview_empty": "選擇結果以預覽",
|
||||
"open_full": "開啟",
|
||||
"view_label": "檢視"
|
||||
}
|
||||
}
|
||||
|
||||
+154
-14
@@ -225,6 +225,10 @@
|
||||
"and_others": "和 {count} 其他人",
|
||||
"draft": "草稿",
|
||||
"starred": "加星标",
|
||||
"pinned": "已置顶",
|
||||
"replied": "已回复",
|
||||
"forwarded": "已转发",
|
||||
"has_attachment": "有附件",
|
||||
"conversations_count": "共 {total} 个会话,当前显示 {count} 个",
|
||||
"conversations_count_plus": "{count}+ 个会话",
|
||||
"conversations_count_simple": "{count} 个会话",
|
||||
@@ -234,7 +238,7 @@
|
||||
"batch_actions": {
|
||||
"select": "选择邮件",
|
||||
"select_all": "全选",
|
||||
"selected_messages": "已選取 {count, plural, one {1} other {#}} 封電子郵件",
|
||||
"selected_messages": "已选取 {count, plural, one {1} other {#}} 封电子邮件",
|
||||
"mark_read": "标记为已读",
|
||||
"mark_unread": "标记为未读",
|
||||
"delete": "删除",
|
||||
@@ -292,6 +296,7 @@
|
||||
"forward": "转发",
|
||||
"delete": "删除",
|
||||
"archive": "归档",
|
||||
"archive_mailbox_not_found": "Archive mailbox not found - cannot archive email",
|
||||
"star": "加星标",
|
||||
"unstar": "取消星标",
|
||||
"mark_unread": "标记为未读",
|
||||
@@ -303,12 +308,10 @@
|
||||
"move": "移动",
|
||||
"print": "打印",
|
||||
"view_source": "查看源码",
|
||||
|
||||
"fullscreen": "全屏",
|
||||
|
||||
"exit_fullscreen": "退出全屏",
|
||||
"export_email": "导出为 .eml",
|
||||
"forward_as_attachment": "Forward as attachment",
|
||||
"forward_as_attachment": "作为附件转发",
|
||||
"import_email": "导入 .eml 或 .zip",
|
||||
"keyboard_shortcuts": "键盘快捷键(?)",
|
||||
"email_source": "邮件源码",
|
||||
@@ -580,6 +583,12 @@
|
||||
"email_composer": {
|
||||
"read_receipt_on": "已请求已读回执(点击以关闭)",
|
||||
"read_receipt_off": "请求已读回执",
|
||||
"dsn_on": "已请求投递通知(点击禁用)",
|
||||
"dsn_off": "请求投递通知",
|
||||
"format_plain_text": "切换为纯文本",
|
||||
"format_rich_text": "切换为富文本 (HTML)",
|
||||
"require_tls_on": "已要求加密投递(点击禁用)",
|
||||
"require_tls_off": "要求加密投递 (TLS)",
|
||||
"new_message": "新邮件",
|
||||
"signature_edit_hint": "双击以编辑签名",
|
||||
"reply": "回复",
|
||||
@@ -631,11 +640,12 @@
|
||||
"from_override": {
|
||||
"toggle_off": "覆盖",
|
||||
"toggle_on": "取消覆盖",
|
||||
"toggle_tooltip": "自由编辑发件人姓名和地址。邮件仍通过您的身份发送 - 仅可见的发件人标题发生变化。",
|
||||
"toggle_tooltip": "自由编辑发件人姓名和地址。邮件仍通过您的身份发送。",
|
||||
"name_label": "发件人姓名",
|
||||
"name_placeholder": "姓名",
|
||||
"email_label": "发件人电子邮件地址",
|
||||
"email_placeholder": "alias@example.com"
|
||||
"email_placeholder": "alias@example.com",
|
||||
"envelope_notice": "如果您的服务器不接受此地址作为信封发件人,将改用 {identity},收件人可以在 Return-Path 标头中看到它。"
|
||||
},
|
||||
"use_template": "模板",
|
||||
"save_as_template": "保存为模板",
|
||||
@@ -648,6 +658,8 @@
|
||||
"upload_progress": "正在上传 {uploaded} / {total}",
|
||||
"upload_cancel": "取消上传",
|
||||
"upload_failed": "无法上传 {filename}",
|
||||
"attachment_too_large": "“{name}”超过了服务器允许的大小(每个文件 {max})",
|
||||
"attachments_total_too_large": "附件将超过服务器每封邮件 {max} 的上限",
|
||||
"drop_files": "拖放要附加的文件",
|
||||
"show_less": "收起",
|
||||
"send_failed": "发送邮件失败",
|
||||
@@ -726,7 +738,11 @@
|
||||
"delete_column": "删除列",
|
||||
"toggle_header_row": "切换标题行",
|
||||
"delete_table": "删除表格",
|
||||
"pick_size": "选择大小"
|
||||
"pick_size": "选择大小",
|
||||
"font_size": "字号",
|
||||
"font_size_default": "默认",
|
||||
"background_color": "背景颜色",
|
||||
"remove_background_color": "移除背景颜色"
|
||||
},
|
||||
"send_filing_warning": "已发送,但发送后的清理失败,可能会残留旧草稿。"
|
||||
},
|
||||
@@ -765,6 +781,10 @@
|
||||
"email_sent": "邮件发送成功",
|
||||
"email_deleted": "邮件已删除",
|
||||
"email_archived": "邮件已归档",
|
||||
"emails_deleted": "{count, plural, other {已删除 # 封邮件}}",
|
||||
"emails_archived": "{count, plural, other {已归档 # 封邮件}}",
|
||||
"error_archiving": "归档邮件失败",
|
||||
"error_updating": "更新邮件失败",
|
||||
"email_starred": "邮件已加星标",
|
||||
"email_unstarred": "邮件已取消星标",
|
||||
"email_marked_read": "邮件已标记为已读",
|
||||
@@ -1143,7 +1163,9 @@
|
||||
"enabled": "邮件通知",
|
||||
"enabled_desc": "新邮件到达时显示通知",
|
||||
"sound": "通知声音",
|
||||
"sound_desc": "新邮件到达时播放音频提醒"
|
||||
"sound_desc": "新邮件到达时播放音频提醒",
|
||||
"inbox_only": "Inbox only",
|
||||
"inbox_only_desc": "Only notify for mail that lands in the Inbox; skip mail a filter files into another folder"
|
||||
},
|
||||
"calendar": {
|
||||
"title": "日历通知",
|
||||
@@ -1172,6 +1194,7 @@
|
||||
"status_busy": "工作中…",
|
||||
"status_inactive": "在此设备上未启用",
|
||||
"status_unsupported": "此浏览器不支持 Web Push",
|
||||
"status_lite": "Web Push is not available in Bulwark Lite; it needs the Bulwark server.",
|
||||
"title": "后台通知",
|
||||
"devices_title": "已注册的设备",
|
||||
"devices_desc": "已注册接收此账户后台通知的所有设备。撤销后,该设备会立即停止接收。",
|
||||
@@ -1238,6 +1261,10 @@
|
||||
}
|
||||
},
|
||||
"email_behavior": {
|
||||
"clear_search_on_folder_change": {
|
||||
"label": "切换文件夹时清除搜索",
|
||||
"description": "点击文件夹(收件箱、已发送等)会清除当前搜索并浏览该文件夹。关闭时,搜索会保留并在切换到的文件夹中重新运行。"
|
||||
},
|
||||
"request_read_receipt": {
|
||||
"label": "默认请求已读回执",
|
||||
"description": "撰写新邮件时预先启用已读回执请求。"
|
||||
@@ -1321,6 +1348,12 @@
|
||||
"label": "自动选择回复地址",
|
||||
"description": "回复时自动将发件人地址切换为最初收到该邮件的身份"
|
||||
},
|
||||
"reply_identity_match": {
|
||||
"label": "接收地址匹配",
|
||||
"description": "哪些接收地址算作您的地址。“仅精确地址”只从已配置的身份中选择;“相同域名”还会将身份域名下的其他任何地址视为 catch-all 别名,并将发件人标头改写为该地址。如果这些地址是分发列表,请选择精确地址。",
|
||||
"exact": "仅精确地址",
|
||||
"domain": "我的域名下的任意地址"
|
||||
},
|
||||
"signature_position": {
|
||||
"label": "签名位置",
|
||||
"description": "在回复和转发中插入签名的位置。位于引用文本上方时,可作为回复的自然结尾;位于下方时,保持原始邮件连贯。",
|
||||
@@ -1746,7 +1779,9 @@
|
||||
"warnings": {
|
||||
"end_before_start": "结束日期必须晚于开始日期",
|
||||
"start_in_past": "开始日期已在过去",
|
||||
"empty_body": "邮件正文为空--收件人将收到空白回复"
|
||||
"empty_body": "邮件正文为空--收件人将收到空白回复",
|
||||
"subject_too_long": "主题对服务器来说太长(最多 {max} 字节)。",
|
||||
"body_too_long": "消息对服务器来说太长(含格式最多 {max} 字节)。"
|
||||
}
|
||||
},
|
||||
"folders": {
|
||||
@@ -1763,6 +1798,8 @@
|
||||
"role_trash": "已删除",
|
||||
"role_junk": "垃圾邮件",
|
||||
"role_archive": "归档",
|
||||
"role_scheduled": "Scheduled",
|
||||
"role_snoozed": "Snoozed",
|
||||
"role_none": "无",
|
||||
"create_folder": "创建文件夹",
|
||||
"create_subfolder": "新建子文件夹",
|
||||
@@ -1882,7 +1919,9 @@
|
||||
"categories_description": "重命名联系人类别",
|
||||
"no_categories": "未找到类别",
|
||||
"group_by_letter_description": "在联系人列表中显示按字母顺序排列的分节标题",
|
||||
"group_by_letter_label": "按首字母分组"
|
||||
"group_by_letter_label": "按首字母分组",
|
||||
"sort_by_last_name_label": "按姓氏排序",
|
||||
"sort_by_last_name_description": "按姓氏排列联系人列表,使家庭成员显示在一起"
|
||||
},
|
||||
"downloads": {
|
||||
"title": "下载",
|
||||
@@ -1963,6 +2002,7 @@
|
||||
"actions": "操作",
|
||||
"add_action": "添加操作",
|
||||
"stop_processing": "停止处理后续规则",
|
||||
"include_spam": "同时移动被标记为垃圾邮件的邮件",
|
||||
"attachment_type_placeholder": "例如:pdf、doc、jpg",
|
||||
"value_placeholder_multi": "值(多个用逗号分隔)",
|
||||
"condition_fields": {
|
||||
@@ -2004,6 +2044,8 @@
|
||||
"copy_to_folder": "选择文件夹",
|
||||
"forward_to": "转发至邮箱地址",
|
||||
"forward_placeholder": "email@example.com",
|
||||
"forward_keep_copy": "保留副本",
|
||||
"forward_limit": "此服务器每封邮件的转发上限:{count}。多余的转发将被跳过。",
|
||||
"reject_message": "拒绝留言",
|
||||
"reject_placeholder": "您的邮件已被拒绝",
|
||||
"label_name": "标签名称",
|
||||
@@ -2207,6 +2249,7 @@
|
||||
"new_subfolder": "新建子文件夹...",
|
||||
"new_folder": "新建文件夹...",
|
||||
"rename": "重命名...",
|
||||
"share": "共享...",
|
||||
"import_email": "导入 .eml 或 .zip...",
|
||||
"empty_folder": "清空文件夹",
|
||||
"empty_folder_generic": "清空文件夹",
|
||||
@@ -2234,6 +2277,9 @@
|
||||
"toast_error_delete": "删除文件夹失败",
|
||||
"toast_error_delete_has_children": "文件夹包含子文件夹,请先将其删除。",
|
||||
"toast_error_delete_has_email": "文件夹不为空,请先清空它。",
|
||||
"delete_with_contents_title": "文件夹不为空",
|
||||
"delete_with_contents_message": "文件夹“{name}”中仍有 {count} 封邮件。删除该文件夹及其所有邮件?此操作无法撤销。",
|
||||
"delete_with_contents_confirm": "删除文件夹和邮件",
|
||||
"placeholder_folder_name": "文件夹名称",
|
||||
"create": "创建",
|
||||
"rename_confirm": "重命名"
|
||||
@@ -2270,7 +2316,8 @@
|
||||
"search": "聚焦搜索框",
|
||||
"help": "显示快捷方式",
|
||||
"refresh": "刷新邮件",
|
||||
"select_all": "全选"
|
||||
"select_all": "全选",
|
||||
"global_search": "全局搜索"
|
||||
},
|
||||
"threads": {
|
||||
"expand_collapse": "展开/折叠会话"
|
||||
@@ -2721,7 +2768,10 @@
|
||||
"any_month": "任意月份",
|
||||
"has_email": "有邮箱",
|
||||
"has_phone": "有电话",
|
||||
"has_photo": "有照片"
|
||||
"has_photo": "有照片",
|
||||
"sort_by": "排序方式",
|
||||
"sort_first_name": "名",
|
||||
"sort_last_name": "姓"
|
||||
},
|
||||
"open_categories": "打开分类"
|
||||
},
|
||||
@@ -2758,6 +2808,10 @@
|
||||
"duplicate": "复制",
|
||||
"today_header": "今天",
|
||||
"tomorrow_header": "明天",
|
||||
"agenda_show_earlier": "显示更早的日程",
|
||||
"agenda_loading": "加载中…",
|
||||
"agenda_range_start": "显示 {date} 起的日程",
|
||||
"agenda_range_end": "显示至 {date} 的日程",
|
||||
"export_ics": "导出为 .ics",
|
||||
"copy_title": "复制标题",
|
||||
"copy_link": "复制会议链接",
|
||||
@@ -2793,6 +2847,14 @@
|
||||
"color": "颜色"
|
||||
},
|
||||
"participants": {
|
||||
"availability": {
|
||||
"title": "空闲状态",
|
||||
"free": "空闲",
|
||||
"busy": "忙碌",
|
||||
"tentative": "暂定",
|
||||
"unknown": "不是此服务器上的用户",
|
||||
"checking": "正在检查…"
|
||||
},
|
||||
"title": "参加者",
|
||||
"add": "添加参与者",
|
||||
"organizer": "组织者",
|
||||
@@ -2867,7 +2929,10 @@
|
||||
"all_events": "所有活动",
|
||||
"cancel": "取消",
|
||||
"save": "保存",
|
||||
"delete": "删除"
|
||||
"delete": "删除",
|
||||
"rsvp_title": "回复重复事件",
|
||||
"rsvp_description": "这是一个重复事件。您的回复适用于哪些事件?",
|
||||
"respond": "回复"
|
||||
},
|
||||
"alerts": {
|
||||
"title": "提醒",
|
||||
@@ -2905,6 +2970,8 @@
|
||||
"show_time_in_month_view_desc": "在月历视图中显示事件时间",
|
||||
"show_week_numbers": "显示周数",
|
||||
"show_week_numbers_desc": "在迷你日历中显示周数",
|
||||
"calendar_free_scroll": "自由滚动",
|
||||
"calendar_free_scroll_desc": "连续滚动浏览月、周和日,而不是一次只显示一个时段",
|
||||
"enable_tasks": "启用任务",
|
||||
"enable_tasks_desc": "在日历中显示任务视图以管理待办事项",
|
||||
"show_tasks_on_calendar": "在日历上显示任务",
|
||||
@@ -2916,6 +2983,9 @@
|
||||
"hover_preview_delay_1s": "1秒延迟",
|
||||
"hover_preview_delay_2s": "2秒延迟",
|
||||
"hover_preview_off": "关闭",
|
||||
"organizer_identity": "以此身份组织邀请",
|
||||
"organizer_identity_desc": "邀请他人参加活动时用作组织者的日历地址。",
|
||||
"organizer_identity_failed": "无法更改组织者地址",
|
||||
"show_birthday_calendar": "联系人生日日历",
|
||||
"show_birthday_calendar_desc": "显示包含联系人生日的虚拟日历"
|
||||
},
|
||||
@@ -2947,6 +3017,9 @@
|
||||
"alert_now": "现在开始",
|
||||
"alert_in_minutes": "{count} 分钟后",
|
||||
"invitation_sent": "已发送邀请",
|
||||
"invitations_denied_title": "无法发送邀请",
|
||||
"invitations_denied": "服务器拒绝发送邀请:{reason}。是否不发送邀请直接保存活动?",
|
||||
"save_without_invitations": "不发送邀请并保存",
|
||||
"rsvp_updated": "回复已更新",
|
||||
"rsvp_error": "无法更新回复",
|
||||
"event_duplicated": "活动已复制",
|
||||
@@ -2975,7 +3048,7 @@
|
||||
"supported_formats": "支持 iCalendar (.ics) 文件",
|
||||
"url_description": "输入外部 iCalendar (.ics) 源的 URL 以导入事件。",
|
||||
"url_placeholder": "https://example.com/calendar.ics",
|
||||
"url_hint": "支持 CalDAV 和 iCalendar (.ics) URL",
|
||||
"url_hint": "支持 iCalendar (.ics) URL",
|
||||
"fetch": "获取",
|
||||
"invalid_url": "请输入有效的 URL",
|
||||
"url_fetch_failed": "无法从 URL 获取日历",
|
||||
@@ -3164,6 +3237,8 @@
|
||||
"has_attachment": "附件",
|
||||
"date_after": "后",
|
||||
"date_before": "前",
|
||||
"size_min": "大于 (KB)",
|
||||
"size_max": "小于 (KB)",
|
||||
"starred": "加星标",
|
||||
"unread": "未读",
|
||||
"read": "已读",
|
||||
@@ -3205,6 +3280,10 @@
|
||||
"hint": "点击左侧的任意邮件即可开始,或参加下方的导览。"
|
||||
},
|
||||
"files": {
|
||||
"office_edit": "编辑文档",
|
||||
"office_loading": "正在打开文档编辑器…",
|
||||
"office_error": "无法打开文档编辑器。",
|
||||
"office_close": "关闭编辑器",
|
||||
"open_in_new_tab": "在新标签页中打开",
|
||||
"title": "文件",
|
||||
"search_placeholder": "搜索文件...",
|
||||
@@ -3217,6 +3296,8 @@
|
||||
"rename": "重命名",
|
||||
"rename_title": "重命名",
|
||||
"new_name": "新名称",
|
||||
"name_forbidden_chars": "服务器不允许名称中包含这些字符:{chars}",
|
||||
"name_reserved": "此名称已被服务器保留。",
|
||||
"delete": "删除",
|
||||
"delete_confirm_title": "删除资源",
|
||||
"delete_confirm_message": "您确定要删除\"{name}\"吗?此操作无法撤消。",
|
||||
@@ -3451,6 +3532,25 @@
|
||||
"unified_mailbox": {
|
||||
"search_unavailable": "统一视图中无法使用搜索"
|
||||
},
|
||||
"calendar_event_notifications": {
|
||||
"someone": "有人",
|
||||
"invited": "{name} 邀请您参加“{title}”",
|
||||
"updated": "{name} 更新了“{title}”",
|
||||
"cancelled": "{name} 取消了“{title}”",
|
||||
"untitled": "无标题活动"
|
||||
},
|
||||
"share_notifications": {
|
||||
"someone": "有人",
|
||||
"shared": "{name} 与您共享了{kind}“{object}”",
|
||||
"changed": "{name} 更改了您对{kind}“{object}”的访问权限",
|
||||
"revoked": "{name} 移除了您对{kind}“{object}”的访问权限",
|
||||
"object": {
|
||||
"folder": "文件夹",
|
||||
"calendar": "日历",
|
||||
"address_book": "通讯录",
|
||||
"files": "文件目录"
|
||||
}
|
||||
},
|
||||
"sharing": {
|
||||
"title": "共享「{name}」",
|
||||
"description": "向此服务器上的其他用户或群组授予访问权限。更改会立即生效。",
|
||||
@@ -3501,5 +3601,45 @@
|
||||
"copy_event": "复制日程链接",
|
||||
"copied": "链接已复制",
|
||||
"copy_failed": "无法复制链接"
|
||||
},
|
||||
"plugins": {
|
||||
"oauth_callback": {
|
||||
"completing": "正在完成授权…",
|
||||
"completing_message": "正在建立连接,请稍候。",
|
||||
"received_title": "已收到授权",
|
||||
"received_message": "您可以返回发起连接的标签页。",
|
||||
"failed_title": "授权失败",
|
||||
"denied": "授权被拒绝。",
|
||||
"failed_detail": "授权失败:{error}",
|
||||
"missing_params": "缺少授权码或状态。",
|
||||
"delivery_failed": "无法将授权结果传递到应用标签页。请关闭此窗口并重试。"
|
||||
}
|
||||
},
|
||||
"global_search": {
|
||||
"title": "全局搜索",
|
||||
"placeholder": "搜索邮件、联系人、日历、文件...",
|
||||
"scope_label": "搜索范围",
|
||||
"account_label": "账户",
|
||||
"scope_all": "全部",
|
||||
"scope_mail": "邮件",
|
||||
"scope_contacts": "联系人",
|
||||
"scope_calendar": "日历",
|
||||
"scope_files": "文件",
|
||||
"all_accounts": "所有账户",
|
||||
"show_all": "显示全部结果",
|
||||
"no_results": "未找到“{query}”的结果",
|
||||
"server_hint": "完整单词在服务器上搜索;部分单词匹配已加载的内容",
|
||||
"searching": "搜索中...",
|
||||
"account_error": "无法搜索 {account}",
|
||||
"recent_searches": "最近搜索",
|
||||
"recurring": "重复",
|
||||
"no_subject": "(no subject)",
|
||||
"empty_hint": "在所有账户中搜索邮件、联系人、日程和文件。可尝试 from:、subject:、has:attachment、after:2026-01-01。",
|
||||
"flat_by_date": "按日期平铺",
|
||||
"grouped": "分组",
|
||||
"load_more": "加载更多",
|
||||
"preview_empty": "选择一个结果以预览",
|
||||
"open_full": "打开",
|
||||
"view_label": "视图"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
const { getDefaultConfig } = require('expo/metro-config');
|
||||
|
||||
const config = getDefaultConfig(__dirname);
|
||||
|
||||
// babel.config.js rewrites lucide-react-native imports to per-icon modules
|
||||
// under dist/ (scripts/babel-plugin-lucide-imports.js). The package's
|
||||
// `exports` map only lists its barrel, so resolve those paths as plain files
|
||||
// instead of letting Metro warn about every one of them.
|
||||
const LUCIDE_DEEP_IMPORT = 'lucide-react-native/dist/';
|
||||
const upstreamResolveRequest = config.resolver.resolveRequest;
|
||||
|
||||
config.resolver.resolveRequest = (context, moduleName, platform) => {
|
||||
const resolve = upstreamResolveRequest ?? context.resolveRequest;
|
||||
if (moduleName.startsWith(LUCIDE_DEEP_IMPORT)) {
|
||||
return resolve({ ...context, unstable_enablePackageExports: false }, moduleName, platform);
|
||||
}
|
||||
return resolve(context, moduleName, platform);
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "bulwark-mobile",
|
||||
"version": "0.1.60",
|
||||
"version": "0.1.63",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bulwark-mobile",
|
||||
"version": "0.1.60",
|
||||
"version": "0.1.63",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bulwark-mobile",
|
||||
"version": "0.1.60",
|
||||
"version": "0.1.63",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// Babel plugin: rewrite named imports from the `lucide-react-native` barrel to
|
||||
// the per-icon modules they re-export, so the bundle only contains (and the
|
||||
// app only evaluates) the icons it uses. The barrel re-exports all ~1,700
|
||||
// icons, and Metro has no tree shaking, so one `import { Mail }` used to pull
|
||||
// in and run every icon at startup.
|
||||
//
|
||||
// import { Mail, Trash2 as Bin } from 'lucide-react-native';
|
||||
// becomes
|
||||
// import Mail from 'lucide-react-native/dist/esm/icons/mail.js';
|
||||
// import Bin from 'lucide-react-native/dist/esm/icons/trash-2.js';
|
||||
//
|
||||
// The name -> file table is read from the package's own ESM barrel, so icon
|
||||
// aliases (`MailIcon`, `LucideMail`, renamed icons) resolve exactly as they do
|
||||
// through the barrel. metro.config.js resolves these deep paths, which the
|
||||
// package's `exports` map does not list.
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const PACKAGE = 'lucide-react-native';
|
||||
const ESM_DIR = 'dist/esm';
|
||||
|
||||
/** Map every name the barrel exports to `{ source, imported }`. */
|
||||
function loadExportMap() {
|
||||
// `main` is dist/cjs/lucide-react-native.js; the ESM tree sits next to it.
|
||||
const pkgDir = path.resolve(path.dirname(require.resolve(PACKAGE)), '..', '..');
|
||||
const barrel = fs.readFileSync(path.join(pkgDir, ESM_DIR, `${PACKAGE}.js`), 'utf8');
|
||||
const map = new Map();
|
||||
for (const m of barrel.matchAll(/export\s*\{([^}]*)\}\s*from\s*'\.\/([^']+)'/g)) {
|
||||
const source = `${PACKAGE}/${ESM_DIR}/${m[2]}`;
|
||||
for (const spec of m[1].split(',')) {
|
||||
const [imported, exported = imported] = spec.trim().split(/\s+as\s+/);
|
||||
if (imported) map.set(exported, { source, imported });
|
||||
}
|
||||
}
|
||||
if (map.size === 0) {
|
||||
throw new Error(`${PACKAGE}: could not read the export list from its ESM barrel`);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
let exportMap = null;
|
||||
function getExportMap() {
|
||||
if (!exportMap) exportMap = loadExportMap();
|
||||
return exportMap;
|
||||
}
|
||||
|
||||
// A value-less binding (only used as a type) is dropped by the TypeScript
|
||||
// transform anyway; treat it the same way instead of failing the build.
|
||||
function isOnlyUsedAsType(importPath, localName) {
|
||||
const binding = importPath.scope.getBinding(localName);
|
||||
if (!binding) return true;
|
||||
return binding.referencePaths.every((ref) => Boolean(ref.findParent((p) => p.isTSType())));
|
||||
}
|
||||
|
||||
function lucideImportsPlugin({ types: t }) {
|
||||
return {
|
||||
name: 'lucide-direct-imports',
|
||||
visitor: {
|
||||
ImportDeclaration(importPath) {
|
||||
const { node } = importPath;
|
||||
if (node.source.value !== PACKAGE || node.importKind === 'type') return;
|
||||
// Side-effect, default and namespace imports keep using the barrel.
|
||||
if (node.specifiers.length === 0) return;
|
||||
if (!node.specifiers.every((s) => t.isImportSpecifier(s))) return;
|
||||
|
||||
const map = getExportMap();
|
||||
const replacements = [];
|
||||
for (const spec of node.specifiers) {
|
||||
if (spec.importKind === 'type') continue;
|
||||
const name = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
|
||||
const target = map.get(name);
|
||||
if (!target) {
|
||||
if (isOnlyUsedAsType(importPath, spec.local.name)) continue;
|
||||
throw importPath.buildCodeFrameError(`${PACKAGE} has no export named "${name}"`);
|
||||
}
|
||||
const local = t.identifier(spec.local.name);
|
||||
const specifier = target.imported === 'default'
|
||||
? t.importDefaultSpecifier(local)
|
||||
: t.importSpecifier(local, t.identifier(target.imported));
|
||||
replacements.push(t.importDeclaration([specifier], t.stringLiteral(target.source)));
|
||||
}
|
||||
if (replacements.length === 0) importPath.remove();
|
||||
else importPath.replaceWithMultiple(replacements);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = lucideImportsPlugin;
|
||||
module.exports.loadExportMap = loadExportMap;
|
||||
module.exports.PACKAGE = PACKAGE;
|
||||
@@ -14,6 +14,18 @@ const git = (...args) =>
|
||||
const gitInherit = (...args) =>
|
||||
execFileSync('git', args, { cwd: root, stdio: 'inherit' });
|
||||
|
||||
// Pre-flight: the release is created with the GitHub CLI as the last step.
|
||||
// Check it is installed and logged in BEFORE anything is committed, tagged or
|
||||
// pushed - otherwise a missing login leaves a pushed tag with no release (and
|
||||
// no release workflow run), which is exactly what a re-run cannot repair.
|
||||
try {
|
||||
execFileSync('gh', ['auth', 'status'], { cwd: root, stdio: 'ignore' });
|
||||
} catch {
|
||||
console.error('GitHub CLI is not available or not logged in.');
|
||||
console.error('Run `gh auth login` (or set GH_TOKEN), then re-run `npm run bump`.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const dirty = git('status', '--porcelain');
|
||||
if (dirty) {
|
||||
console.error('Working tree is not clean. Commit or stash changes before bumping.');
|
||||
@@ -30,6 +42,15 @@ if (parts.length !== 3 || parts.some(Number.isNaN)) {
|
||||
parts[2] += 1;
|
||||
const next = parts.join('.');
|
||||
|
||||
// android/app/build.gradle derives versionCode as
|
||||
// major * 10000000 + minor * 10000 + patch. Past these limits it would stop
|
||||
// increasing, and Android refuses an update with a lower versionCode.
|
||||
if (parts[1] > 999 || parts[2] > 9999) {
|
||||
console.error(`${next} would break the Android versionCode: minor must stay below 1000 and patch below 10000.`);
|
||||
console.error('Raise the minor or major version in VERSION by hand and commit it, then bump again.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const existingTags = new Set(git('tag', '--list').split(/\r?\n/).filter(Boolean));
|
||||
if (existingTags.has(next)) {
|
||||
console.error(`Tag ${next} already exists.`);
|
||||
@@ -54,10 +75,16 @@ gitInherit('commit', '-m', `chore: update version number to ${next}`);
|
||||
gitInherit('tag', '-a', next, '-m', next);
|
||||
gitInherit('push', '--follow-tags');
|
||||
|
||||
execFileSync(
|
||||
'gh',
|
||||
['release', 'create', next, '--title', next, '--generate-notes'],
|
||||
{ cwd: root, stdio: 'inherit' },
|
||||
);
|
||||
try {
|
||||
execFileSync(
|
||||
'gh',
|
||||
['release', 'create', next, '--title', next, '--generate-notes'],
|
||||
{ cwd: root, stdio: 'inherit' },
|
||||
);
|
||||
} catch {
|
||||
console.error(`The tag ${next} is pushed but the GitHub release could not be created.`);
|
||||
console.error(`Fix the GitHub CLI login and run: gh release create ${next} --title ${next} --generate-notes`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Released ${next}`);
|
||||
|
||||
@@ -61,13 +61,15 @@ const overlayFlat = flatten(overlay);
|
||||
|
||||
// t('key', 'fallback') / t('key', "fallback") / t('key', `fallback`) - the
|
||||
// fallback may span lines; keep it simple: single-quoted or double-quoted,
|
||||
// non-nested strings.
|
||||
const CALL_RE = /\bt\(\s*'([^']+)'\s*,\s*(?:'((?:[^'\\]|\\.)*)'|"((?:[^"\\]|\\.)*)")/g;
|
||||
const KEY_ONLY_RE = /\bt\(\s*'([^']+)'/g;
|
||||
// non-nested strings. `tr` is the same function where `t` is taken (a
|
||||
// template or timer variable).
|
||||
const CALL_RE = /\btr?\(\s*'([^']+)'\s*,\s*(?:'((?:[^'\\]|\\.)*)'|"((?:[^"\\]|\\.)*)")/g;
|
||||
const KEY_ONLY_RE = /\btr?\(\s*'([^']+)'/g;
|
||||
|
||||
const added = [];
|
||||
const missingNoFallback = [];
|
||||
for (const file of walk(SRC)) {
|
||||
// App.tsx (the tab bar and loading screen) sits next to src/.
|
||||
for (const file of [...walk(SRC), join(ROOT, 'App.tsx')]) {
|
||||
const text = readFileSync(file, 'utf8');
|
||||
const withFallback = new Map();
|
||||
for (const m of text.matchAll(CALL_RE)) {
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// List the RN-only overlay keys (locales/rn/en.json) a language has not
|
||||
// translated in locales/rn/<lang>.json yet. Run from the RN repo root:
|
||||
//
|
||||
// node scripts/rn-missing-translations.mjs count per language
|
||||
// node scripts/rn-missing-translations.mjs de the missing keys with their
|
||||
// English text, as nested JSON
|
||||
// node scripts/rn-missing-translations.mjs --prune
|
||||
// drop translated keys English
|
||||
// no longer has
|
||||
//
|
||||
// Keys the vendored webmail catalog of that language already carries are not
|
||||
// listed: they are translated there.
|
||||
import { readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const RN = join(ROOT, 'locales', 'rn');
|
||||
|
||||
function flatten(obj, prefix = '', out = new Map()) {
|
||||
for (const [k, v] of Object.entries(obj)) {
|
||||
const key = prefix ? `${prefix}.${k}` : k;
|
||||
if (v && typeof v === 'object' && !Array.isArray(v)) flatten(v, key, out);
|
||||
else out.set(key, v);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function setDeep(obj, key, value) {
|
||||
const parts = key.split('.');
|
||||
let cur = obj;
|
||||
for (const part of parts.slice(0, -1)) cur = cur[part] ??= {};
|
||||
cur[parts.at(-1)] = value;
|
||||
}
|
||||
|
||||
const readJson = (path) => JSON.parse(readFileSync(path, 'utf8'));
|
||||
|
||||
function missingFor(lang, en) {
|
||||
const done = flatten(readJson(join(RN, `${lang}.json`)));
|
||||
const webmail = flatten(readJson(join(ROOT, 'locales', lang, 'common.json')));
|
||||
return [...en].filter(([key]) => !done.has(key) && !webmail.has(key));
|
||||
}
|
||||
|
||||
const en = flatten(readJson(join(RN, 'en.json')));
|
||||
const lang = process.argv[2];
|
||||
const langs = readdirSync(RN).filter((f) => f.endsWith('.json') && f !== 'en.json').map((f) => f.slice(0, -5));
|
||||
|
||||
if (lang === '--prune') {
|
||||
for (const l of langs.sort()) {
|
||||
const path = join(RN, `${l}.json`);
|
||||
const kept = {};
|
||||
let dropped = 0;
|
||||
for (const [key, value] of flatten(readJson(path))) {
|
||||
if (en.has(key)) setDeep(kept, key, value);
|
||||
else dropped++;
|
||||
}
|
||||
if (dropped) {
|
||||
writeFileSync(path, `${JSON.stringify(kept, null, 2)}\n`);
|
||||
console.log(`${l.padEnd(6)} dropped ${dropped}`);
|
||||
}
|
||||
}
|
||||
} else if (lang) {
|
||||
const out = {};
|
||||
for (const [key, value] of missingFor(lang, en)) setDeep(out, key, value);
|
||||
console.log(JSON.stringify(out, null, 2));
|
||||
} else {
|
||||
for (const l of langs.sort()) console.log(`${l.padEnd(6)} ${missingFor(l, en).length} of ${en.size} missing`);
|
||||
}
|
||||
@@ -14,8 +14,13 @@ const uploadTask = {
|
||||
cancelAsync: vi.fn(async () => undefined),
|
||||
};
|
||||
const createUploadTask = vi.fn((..._args: unknown[]) => uploadTask);
|
||||
const copyAsync = vi.fn(async (_options: { from: string; to: string }) => undefined);
|
||||
const deleteAsync = vi.fn(async (_uri: string, _options?: unknown) => undefined);
|
||||
vi.mock('expo-file-system/legacy', () => ({
|
||||
cacheDirectory: 'file:///cache/',
|
||||
createUploadTask: (...args: unknown[]) => createUploadTask(...args),
|
||||
copyAsync: (options: { from: string; to: string }) => copyAsync(options),
|
||||
deleteAsync: (uri: string, options?: unknown) => deleteAsync(uri, options),
|
||||
FileSystemUploadType: { BINARY_CONTENT: 0, MULTIPART: 1 },
|
||||
}));
|
||||
|
||||
@@ -35,7 +40,7 @@ vi.mock('../jmap-client', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
import { getDownloadUrl, uploadBlob } from '../blob';
|
||||
import { getDownloadUrl, isStaleUploadCopy, uploadBlob } from '../blob';
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -125,4 +130,58 @@ describe('uploadBlob', () => {
|
||||
).rejects.toMatchObject({ name: 'AbortError' });
|
||||
expect(createUploadTask).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('uploads files on disk directly without copying them', async () => {
|
||||
uploadTask.uploadAsync.mockResolvedValueOnce({ status: 200, body: JSON.stringify({ blobId: 'b' }) });
|
||||
await uploadBlob('file:///tmp/a.pdf', 'application/pdf');
|
||||
expect(copyAsync).not.toHaveBeenCalled();
|
||||
expect(deleteAsync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('copies a shared content:// URI into the cache, uploads the copy and deletes it', async () => {
|
||||
uploadTask.uploadAsync.mockResolvedValueOnce({ status: 200, body: JSON.stringify({ blobId: 'blob-3' }) });
|
||||
const shared = 'content://media/external/images/media/1000000027';
|
||||
|
||||
const result = await uploadBlob(shared, 'image/png');
|
||||
|
||||
expect(result.blobId).toBe('blob-3');
|
||||
const { from, to } = copyAsync.mock.calls[0][0];
|
||||
expect(from).toBe(shared);
|
||||
expect(to).toMatch(/^file:\/\/\/cache\/upload-/);
|
||||
expect(createUploadTask.mock.calls[0][1]).toBe(to);
|
||||
expect(deleteAsync).toHaveBeenCalledWith(to, { idempotent: true });
|
||||
});
|
||||
|
||||
it('deletes the cache copy when the upload fails', async () => {
|
||||
uploadTask.uploadAsync.mockResolvedValueOnce({ status: 500, body: 'boom' });
|
||||
await expect(uploadBlob('content://com.example.provider/doc/7', 'application/pdf')).rejects.toThrow(/500/);
|
||||
const { to } = copyAsync.mock.calls[0][0];
|
||||
expect(deleteAsync).toHaveBeenCalledWith(to, { idempotent: true });
|
||||
});
|
||||
|
||||
it('does not upload when the shared file cannot be copied', async () => {
|
||||
copyAsync.mockRejectedValueOnce(new Error('Permission Denial'));
|
||||
await expect(uploadBlob('content://com.example.provider/doc/8', 'image/jpeg')).rejects.toThrow(/Permission Denial/);
|
||||
expect(createUploadTask).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isStaleUploadCopy', () => {
|
||||
it('flags upload copies an earlier run of the app left behind', () => {
|
||||
expect(isStaleUploadCopy('upload-1700000000000-3')).toBe(true);
|
||||
});
|
||||
|
||||
it("leaves this run's copies alone, since they may still be uploading", async () => {
|
||||
uploadTask.uploadAsync.mockResolvedValueOnce({ status: 200, body: JSON.stringify({ blobId: 'b' }) });
|
||||
await uploadBlob('content://com.example.provider/doc/9', 'image/png');
|
||||
const name = copyAsync.mock.calls[0][0].to.replace('file:///cache/', '');
|
||||
|
||||
expect(isStaleUploadCopy(name)).toBe(false);
|
||||
});
|
||||
|
||||
it('ignores other cache files', () => {
|
||||
expect(isStaleUploadCopy('upload-notes.txt')).toBe(false);
|
||||
expect(isStaleUploadCopy('bulwark-exports')).toBe(false);
|
||||
expect(isStaleUploadCopy('x-upload-1700000000000-3')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user