Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bd3867cb3 | ||
|
|
9577387c2f | ||
|
|
7a087b8827 | ||
|
|
7099106802 | ||
|
|
43385d36ee | ||
|
|
4b97f401cb | ||
|
|
af869944cf | ||
|
|
4b90abf3b5 |
No files matched your search
@@ -6,9 +6,10 @@ deliberately high — everything else is ordinary Rust review.
|
|||||||
|
|
||||||
## Before you start
|
## Before you start
|
||||||
|
|
||||||
- Read [`AGENTS.md`](AGENTS.md) for repository conventions and
|
- Read [`AGENTS.md`](AGENTS.md) for repository conventions,
|
||||||
[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for how the daemon, front ends, and SDK fit
|
[`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md) for building, running and debugging from source,
|
||||||
together.
|
and [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for how the daemon, front ends, and SDK fit
|
||||||
|
together. [`docs/README.md`](docs/README.md) indexes the rest.
|
||||||
- Check [`docs/BUGS.md`](docs/BUGS.md) — it is the authoritative issue ledger, and your problem may
|
- Check [`docs/BUGS.md`](docs/BUGS.md) — it is the authoritative issue ledger, and your problem may
|
||||||
already be tracked there with context.
|
already be tracked there with context.
|
||||||
- For anything larger than a fix, open an issue first so the design can be discussed before you
|
- For anything larger than a fix, open an issue first so the design can be discussed before you
|
||||||
@@ -23,8 +24,9 @@ cargo build --workspace
|
|||||||
```
|
```
|
||||||
|
|
||||||
GUI and FUSE targets need the GTK4, libadwaita, WebKitGTK 6, libsecret, D-Bus, and FUSE3
|
GUI and FUSE targets need the GTK4, libadwaita, WebKitGTK 6, libsecret, D-Bus, and FUSE3
|
||||||
development packages listed in the [README](README.md#prerequisites). Minimum supported Rust
|
development packages listed in [`docs/INSTALL.md`](docs/INSTALL.md#build-dependencies). Minimum
|
||||||
version is 1.96.
|
supported Rust version is 1.96. [`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md#running-from-source)
|
||||||
|
explains how to run your build next to, or instead of, the installed service.
|
||||||
|
|
||||||
## Quality gates
|
## Quality gates
|
||||||
|
|
||||||
@@ -51,6 +53,13 @@ use a dedicated test account, never one holding data you cannot lose.
|
|||||||
control-socket work stays off GTK's main thread.
|
control-socket work stays off GTK's main thread.
|
||||||
- **`staging/` and `recovery/` may hold the only copy of user data.** Nothing may clear or purge
|
- **`staging/` and `recovery/` may hold the only copy of user data.** Nothing may clear or purge
|
||||||
them opportunistically.
|
them opportunistically.
|
||||||
|
- **The GUI is fully translated.** Wrap every user-visible string in the helpers from
|
||||||
|
`crates/pdfs-gui/src/i18n.rs`, run `po/update.sh`, and translate the new entries in every
|
||||||
|
catalog in `po/LINGUAS` in the same change. See [`docs/TRANSLATING.md`](docs/TRANSLATING.md).
|
||||||
|
The CLI and daemon messages stay in English.
|
||||||
|
- **Document what users see.** A new command, setting or behaviour belongs in the matching page
|
||||||
|
under `docs/` (user guide, CLI reference or configuration) and in the `[Unreleased]` section of
|
||||||
|
[`docs/CHANGELOG.md`](docs/CHANGELOG.md).
|
||||||
|
|
||||||
## Commits and pull requests
|
## Commits and pull requests
|
||||||
|
|
||||||
|
|||||||
@@ -3474,7 +3474,7 @@ checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pdfs-cli"
|
name = "pdfs-cli"
|
||||||
version = "2.5.0"
|
version = "2.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -3489,7 +3489,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pdfs-core"
|
name = "pdfs-core"
|
||||||
version = "2.5.0"
|
version = "2.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"dbus-secret-service-keyring-store",
|
"dbus-secret-service-keyring-store",
|
||||||
@@ -3513,7 +3513,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pdfs-fuse"
|
name = "pdfs-fuse"
|
||||||
version = "2.5.0"
|
version = "2.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.23.1",
|
"base64 0.23.1",
|
||||||
"fuser",
|
"fuser",
|
||||||
@@ -3533,7 +3533,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pdfs-gui"
|
name = "pdfs-gui"
|
||||||
version = "2.5.0"
|
version = "2.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-channel",
|
"async-channel",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ resolver = "2"
|
|||||||
members = ["crates/pdfs-core", "crates/pdfs-fuse", "crates/pdfs-cli", "crates/pdfs-gui"]
|
members = ["crates/pdfs-core", "crates/pdfs-fuse", "crates/pdfs-cli", "crates/pdfs-gui"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "2.5.0"
|
version = "2.6.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["Nils Pukropp <contact@narl.io>"]
|
authors = ["Nils Pukropp <contact@narl.io>"]
|
||||||
|
|||||||
@@ -4,513 +4,162 @@
|
|||||||
|
|
||||||
# Proton Drive for Linux
|
# Proton Drive for Linux
|
||||||
|
|
||||||
**Files-on-demand Proton Drive for the Linux desktop** — a FUSE virtual mount with block-level
|
**Proton Drive on the Linux desktop.** Your files as a folder that downloads on demand, synced
|
||||||
caching, a scriptable CLI, and a native GTK4 app with tray and search launcher.
|
local folders, Photos, sharing, and a native GTK4 app with tray and search launcher, plus a
|
||||||
|
scriptable CLI.
|
||||||
|
|
||||||
[](https://github.com/narrrl/proton-drive-linux/actions/workflows/ci.yml)
|
[](https://github.com/narrrl/proton-drive-linux/actions/workflows/ci.yml)
|
||||||
[](https://github.com/narrrl/proton-drive-linux/releases/latest)
|
[](https://github.com/narrrl/proton-drive-linux/releases/latest)
|
||||||
|
[](https://aur.archlinux.org/packages/proton-drive-for-linux)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://www.rust-lang.org)
|
[](https://www.rust-lang.org)
|
||||||
[](#prerequisites)
|
|
||||||
|
|
||||||
<img src="images/files.png" alt="The GTK4 file browser" width="820">
|
<img src="images/files.png" alt="My files in the Proton Drive app" width="820">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> This is an **unofficial**, community-built client. It is not affiliated with, endorsed by, or
|
> This is an **unofficial**, community-built client. It is not affiliated with, endorsed by, or
|
||||||
> supported by Proton AG. "Proton" and "Proton Drive" are trademarks of Proton AG, used here only
|
> supported by Proton AG. "Proton" and "Proton Drive" are trademarks of Proton AG, used here only
|
||||||
> to describe what the software talks to. Use a test account first, and read
|
> to describe what the software talks to. Try it with data you have another copy of first, and
|
||||||
> [`docs/RECOVERY.md`](docs/RECOVERY.md) before trusting it with data you cannot lose.
|
> read [what survives a lost machine](docs/RECOVERY.md) before you rely on it.
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Debian / Ubuntu
|
|
||||||
sudo apt install ./proton-drive-linux_*.deb
|
|
||||||
|
|
||||||
# Fedora
|
|
||||||
sudo dnf install ./proton-drive-linux-*.rpm
|
|
||||||
|
|
||||||
# Arch Linux (AUR)
|
|
||||||
paru -S proton-drive-for-linux # or -git / -bin
|
|
||||||
```
|
|
||||||
|
|
||||||
Packages for each release are attached to the [latest
|
|
||||||
release](https://github.com/narrrl/proton-drive-linux/releases/latest); see
|
|
||||||
[Installation & Packages](#installation--packages) for details and
|
|
||||||
[Building from Source](#building-from-source) to compile it yourself.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pdfs login # sign in (SRP + 2FA, credentials go to the system keyring)
|
|
||||||
pdfs status # mount state, queued writes, cache usage
|
|
||||||
pdfs-app # the desktop app
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contents
|
|
||||||
|
|
||||||
- [Features](#features) · [Quick search](#quick-search-and-media-opening) · [Opening files](#choosing-how-files-open)
|
|
||||||
- [Selective sync](#selective-sync-pdfsignore) · [Diagnostics](#diagnostics--maintenance) · [Scripting](#scripting---json)
|
|
||||||
- [Performance](#performance--caching) · [Filesystem safety](#filesystem-safety) · [Screenshots](#screenshots)
|
|
||||||
- [Prerequisites](#prerequisites) · [Building](#building-from-source) · [Packages](#installation--packages) · [Releases](#automated-releases-cicd)
|
|
||||||
|
|
||||||
**Documentation:** [Architecture](docs/ARCHITECTURE.md) · [Testing](docs/TESTING.md) ·
|
|
||||||
[Recovery](docs/RECOVERY.md) · [Development](docs/DEVELOPMENT.md) · [Translating](docs/TRANSLATING.md) ·
|
|
||||||
[Known issues](docs/BUGS.md) · [Changelog](docs/CHANGELOG.md) ·
|
|
||||||
[Contributing](CONTRIBUTING.md) · [Security policy](SECURITY.md)
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Files-on-Demand (FUSE)**: Mount your Proton Drive as a virtual filesystem where files are downloaded only when opened, utilizing smart block-level caching and disk-backed writes.
|
**Files**
|
||||||
- **Command-Line Interface (CLI)**: Manage your drive, authenticate, and monitor sync status directly from the terminal.
|
|
||||||
- **Non-Blocking GTK4 Desktop App**: Browse files, manage pins, configure options, and monitor active transfers through a modern, native GUI with a fully non-blocking asynchronous main loop.
|
|
||||||
- **File Thumbnails**: Browse locally cached image previews in Files, Shared, and Trash, including embedded previews from camera RAW files. Build a whole folder tree from the Files toolbar and resize the grid from the status bar.
|
|
||||||
- **Shared Links & Invites**: Browse files shared with you by other users, and view/manage your own public shared links directly in the GUI.
|
|
||||||
- **Local Backup (Computers)**: Sync and back up local directories (like Downloads, Documents, Pictures, etc.) directly to your Proton Drive account.
|
|
||||||
- **Locations**: One page (and `pdfs locations`) listing every place Proton Drive occupies on this machine — the main mount plus each backed-up folder — with its mode, sync state, and whether it is read-only. Switching a folder between a full local copy and on-demand happens here.
|
|
||||||
- **System Tray Integration**: Background indicator for status monitoring, quick actions, and fast sync controls.
|
|
||||||
- **Unified Search Launcher (HUD)**: A resident Google Drive-style launcher (`pdfs-prompt`) that searches Proton Drive and local files together, ranks the best matches, tolerates abbreviations and typos, and is ideal for a system-wide hotkey. It can also present the same search in your own launcher (`--dmenu`) or, searching live as you type, in `fzf` (`--fzf`).
|
|
||||||
- **Secure Credential Storage**: Integrates with the system Secret Service (GNOME Keyring, KWallet, etc.) with smart in-memory credential caching to avoid UI thread blockages.
|
|
||||||
- **Proton Photos Support**: Access your Proton Photos timeline, view thumbnails, and download backed-up media natively (available in the GUI as a navigation tab and via the CLI).
|
|
||||||
- **File Version History**: Every revision Proton Drive still holds for a file, from the browser's details pane (**Versions**) or `pdfs versions list|restore|save|rm`. Restoring is server-side — no re-upload — and an old version can be written out to a local file without touching the live one.
|
|
||||||
- **Photo Favourites**: Star a photo in the lightbox, filter the gallery to favourites, or use `pdfs favorite <uid>` / `pdfs photos --favorites`.
|
|
||||||
- **Photo Albums**: Browse your albums — including the ones shared with you — from the Albums view of the Photos page, or with `pdfs albums` / `pdfs album <uid>`. Album contents open in the same gallery as the timeline.
|
|
||||||
- **Places**: The Places view of the Photos page groups photos by the town they were taken in, using the location stored with each photo, as cards or on a world map. Towns are looked up offline in [GeoNames](https://www.geonames.org/) data (CC BY 4.0), and the map is drawn offline from [Natural Earth](https://www.naturalearthdata.com/) land outlines and country borders (public domain). Each town is marked with one of its photos, and zoomed in, every photo sits where it was taken. A street map from [OpenFreeMap](https://openfreemap.org/) (© [OpenStreetMap](https://www.openstreetmap.org/copyright) contributors) can be turned on under Appearance; it is off by default because the tile server learns which parts of the world you look at. Install `iso-codes` for country names in your language.
|
|
||||||
- **Human Verification (CAPTCHA) Recovery**: Detects sign-in gates (VPN/new IP challenges) and launches an embedded `WebKitWebView` dialog to safely complete the challenge, transparently retrying authentication with the earned token.
|
|
||||||
- **Selective Sync (`.pdfsignore`)**: Keep build trees, dependency directories, and editor leftovers out of synced folders using gitignore-style rules.
|
|
||||||
- **Data-Safe Offline Writes**: Durable scratch/staging files and a transactional pending queue preserve acknowledged writes across network failures and restarts.
|
|
||||||
|
|
||||||
## Quick Search and Media Opening
|
- **Your Drive as a folder.** `~/ProtonDrive` works with every program. Files download when they
|
||||||
|
are opened, a block at a time, so a video starts playing and a large archive can be read
|
||||||
|
without fetching all of it.
|
||||||
|
- **Available offline.** Keep chosen files and folders fully downloaded; everything else is
|
||||||
|
cached within a size limit you set.
|
||||||
|
- **Synced folders.** Back up folders such as `~/Documents` under your computer in Proton Drive,
|
||||||
|
as a full two-way copy or online only. Gitignore-style `.pdfsignore` rules, conflict copies
|
||||||
|
instead of overwrites, pause and bandwidth limits.
|
||||||
|
- **Safe writes.** Changes are staged on disk and queued before they are acknowledged, so they
|
||||||
|
survive a lost connection, a crash or a reboot and upload when they can.
|
||||||
|
|
||||||
`pdfs-prompt` searches Drive metadata and the local home-directory index in one request. Results are ranked together rather than split by source. Matching is case-insensitive and supports exact names, prefixes, parent paths, multiple terms, punctuation-separated words, ordered abbreviations, common typos, and adjacent transpositions.
|
**Desktop app**
|
||||||
|
|
||||||
The prompt stays resident after its first launch, so invoking the shortcut again reuses and resets the existing window. Drive folders open from the mount. Audio and video results also open through FUSE, allowing media players to seek and request ranges without downloading the entire file first; ordinary Drive files are materialized into the local cache before opening.
|
- **Browse** My files, Shared with me, Shared by me, Computers and Trash, with thumbnails
|
||||||
|
(including camera RAW), drag and drop, details and **version history**.
|
||||||
|
- **Photos.** Timeline with favorites, albums, and Places on a map; RAW+JPEG pairs shown as one
|
||||||
|
photo; duplicate finder; slideshow; **import from Google Photos** (Takeout).
|
||||||
|
- **Sharing.** Invite people as viewer, editor or admin, create public links with password and
|
||||||
|
expiry, accept invitations.
|
||||||
|
- **Sync overview.** What is uploading, what is waiting, what failed and why, and a history of
|
||||||
|
what happened.
|
||||||
|
- **Tray icon** with status, pause and quick actions, and a **search launcher** (`pdfs-prompt`)
|
||||||
|
that searches Proton Drive and your home folder together, as its own window, through fuzzel,
|
||||||
|
rofi or wofi, or live in `fzf`.
|
||||||
|
- **Restore a computer.** Continue a backup on a new machine and bring its folders back in one
|
||||||
|
step.
|
||||||
|
- **Sign-in** with two-factor authentication and CAPTCHA; the session is kept in the system
|
||||||
|
keyring.
|
||||||
|
- Available in English, German, Spanish, French, Italian, Dutch, Polish and Brazilian
|
||||||
|
Portuguese.
|
||||||
|
|
||||||
### Using your own launcher (fuzzel, rofi, wofi, …)
|
**Command line**
|
||||||
|
|
||||||
`pdfs-prompt --dmenu` runs the same search through an external dmenu-style
|
- About 55 `pdfs` commands cover files, sync, sharing, Photos, Trash, versions and diagnostics.
|
||||||
launcher instead of the built-in window, so the HUD matches the rest of a
|
Query commands print JSON with `--json`.
|
||||||
tiling-WM desktop:
|
|
||||||
|
|
||||||
```bash
|
## Install
|
||||||
pdfs-prompt --dmenu # fuzzel/rofi/wofi/tofi/bemenu/dmenu, whichever is installed
|
|
||||||
pdfs-prompt --dmenu --menu 'fuzzel --dmenu --width 60'
|
|
||||||
pdfs-prompt --dmenu --query invoice # skip straight to a search
|
|
||||||
```
|
|
||||||
|
|
||||||
A launcher filters a fixed list — it cannot ask for a new one per keystroke — so
|
| Distribution | Package |
|
||||||
searching takes two steps, and the prompt says which step you are on:
|
|
||||||
|
|
||||||
- `Search Drive ›` lists your pinned files. Type anything that isn't one of them
|
|
||||||
and press Enter to search for it.
|
|
||||||
- `Drive: invoice ›` lists the results. Enter opens one; typing something else
|
|
||||||
and pressing Enter searches again. Escape closes.
|
|
||||||
|
|
||||||
`--query` skips the first step. fuzzel and rofi also get file-type icons.
|
|
||||||
|
|
||||||
To make an existing keybinding use it without re-binding, and to pin the
|
|
||||||
launcher command, set them in `config.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"prompt": {
|
|
||||||
"mode": "dmenu",
|
|
||||||
"menu": ["fuzzel", "--dmenu", "--width", "60"],
|
|
||||||
"menu_limit": 50
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`--gtk` overrides `"mode": "dmenu"` for one invocation. A `{prompt}` token
|
|
||||||
anywhere in `menu` is replaced by the prompt text; without one, the launcher's
|
|
||||||
own prompt flag is appended.
|
|
||||||
|
|
||||||
### Live launcher search (fzf)
|
|
||||||
|
|
||||||
The two-step above exists because no dmenu-style launcher can call back for new
|
|
||||||
results per keystroke. `fzf` can, so `pdfs-prompt --fzf` searches as you type,
|
|
||||||
exactly like the built-in window:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pdfs-prompt --fzf # pinned files, then live results as you type
|
|
||||||
pdfs-prompt --fzf --query invoice # start on a search
|
|
||||||
```
|
|
||||||
|
|
||||||
Every keystroke re-queries the daemon (debounced, like the GTK prompt) and fzf's
|
|
||||||
own matcher is switched off, so the ordering you see is the daemon's ranking —
|
|
||||||
abbreviations and typos included, which a launcher filtering a fixed list cannot
|
|
||||||
do. Enter opens; Escape closes.
|
|
||||||
|
|
||||||
fzf is a terminal program, so when it is started without one — from a WM
|
|
||||||
keybinding — it spawns a terminal for itself: foot, ghostty, kitty, alacritty,
|
|
||||||
wezterm or xterm, whichever is installed. Each is launched with the app-id/class
|
|
||||||
`pdfs-prompt`, so one window rule floats it whichever terminal you have. In
|
|
||||||
Hyprland:
|
|
||||||
|
|
||||||
```
|
|
||||||
windowrulev2 = float, class:^(pdfs-prompt)$
|
|
||||||
windowrulev2 = size 900 500, class:^(pdfs-prompt)$
|
|
||||||
windowrulev2 = center, class:^(pdfs-prompt)$
|
|
||||||
bind = SUPER, space, exec, pdfs-prompt --fzf
|
|
||||||
```
|
|
||||||
|
|
||||||
Set it as the default front end, and pick the terminal explicitly, in
|
|
||||||
`config.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"prompt": {
|
|
||||||
"mode": "fzf",
|
|
||||||
"terminal": ["foot", "--app-id=pdfs-prompt", "--"],
|
|
||||||
"menu_limit": 50
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
A `{cmd}` token anywhere in `terminal` is replaced by the command to run;
|
|
||||||
without one it is appended, which is what every detected terminal expects.
|
|
||||||
|
|
||||||
## Choosing How Files Open
|
|
||||||
|
|
||||||
By default every result is handed to `xdg-open`. An `open_with` block in
|
|
||||||
`config.json` overrides that per file type — for instance opening text in
|
|
||||||
Neovim inside Alacritty, while everything else still goes to the desktop:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"open_with": {
|
|
||||||
"terminal": ["alacritty", "-e"],
|
|
||||||
"rules": [
|
|
||||||
{ "match": ["@text"], "command": ["nvim"], "terminal": true },
|
|
||||||
{ "match": ["*.png", "*.jpg"], "command": ["imv"] }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- `match` takes file-name globs (`*.md`, `notes-*.txt`) or the classes `@dir`,
|
|
||||||
`@text`, `@document`, `@image`, `@media`, `@any`. First matching rule wins.
|
|
||||||
- `command` is argv, not a shell line. A `{}` token is replaced by the path;
|
|
||||||
without one the path is appended. `$VAR` tokens expand from the environment,
|
|
||||||
so `["$EDITOR"]` follows your editor.
|
|
||||||
- `"terminal": true` wraps the command in `terminal`. When that is unset, the
|
|
||||||
terminal comes from `$PDFS_TERMINAL`, then `$TERMINAL`, then the first known
|
|
||||||
emulator on `PATH`; a bare name like `TERMINAL=alacritty` gains the right
|
|
||||||
"run this" flag automatically.
|
|
||||||
- `"default": [...]` replaces `xdg-open` for everything unmatched.
|
|
||||||
|
|
||||||
The rules apply to the prompt (both front ends) and to the GTK browser, so a
|
|
||||||
file opens the same way wherever it was picked. Patterns are matched against the
|
|
||||||
Drive name — a downloaded file is stored in the cache under its content hash,
|
|
||||||
which says nothing about its type, so matching that would send everything to
|
|
||||||
`xdg-open`.
|
|
||||||
|
|
||||||
## Selective Sync (`.pdfsignore`)
|
|
||||||
|
|
||||||
Two-way synced folders skip paths matched by ignore rules, so syncing a project
|
|
||||||
directory does not upload `node_modules/`, `target/`, or `.git/`.
|
|
||||||
|
|
||||||
Rules come from two places, and both apply:
|
|
||||||
|
|
||||||
1. **Per folder** — a `.pdfsignore` file at the root of the synced folder
|
|
||||||
(`.protonignore` also works). Gitignore syntax, including negation:
|
|
||||||
|
|
||||||
```gitignore
|
|
||||||
# everything build-related
|
|
||||||
build/
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# ...except this one
|
|
||||||
!important.log
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Globally** — an `ignore_patterns` list in `config.json`, applied to every
|
|
||||||
synced folder. When unset, sensible defaults apply: `.git/`, `.hg/`, `.svn/`,
|
|
||||||
`node_modules/`, `target/`, `.venv/`, `__pycache__/`, `*~`, `*.swp`, `*.tmp`,
|
|
||||||
`.DS_Store`, and `Thumbs.db`.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"ignore_patterns": ["node_modules/", "target/", "*.iso"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Set it to `[]` to opt out of the defaults entirely.
|
|
||||||
|
|
||||||
Rules are re-read at the start of every sync pass, so edits take effect on the
|
|
||||||
next pass without restarting the daemon.
|
|
||||||
|
|
||||||
**Ignoring is never destructive.** If a rule starts matching a file that was
|
|
||||||
already synced, its copy on Drive is left untouched — the file simply stops
|
|
||||||
being tracked. Removing the rule later picks the existing remote file back up
|
|
||||||
rather than re-uploading it.
|
|
||||||
|
|
||||||
## Diagnostics & Maintenance
|
|
||||||
|
|
||||||
When something looks wrong, `pdfs diagnose` checks the installation and prints a
|
|
||||||
report. It runs without a daemon on purpose — the state worth diagnosing is
|
|
||||||
usually the state where the daemon will not start:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ pdfs diagnose
|
|
||||||
Paths
|
|
||||||
[ok ] state dir: /home/you/.local/state/proton-drive-linux
|
|
||||||
[ok ] database: /home/you/.local/state/proton-drive-linux/cache.db (170.3 MiB)
|
|
||||||
|
|
||||||
Account
|
|
||||||
[ok ] keyring session: you@proton.me
|
|
||||||
|
|
||||||
Daemon
|
|
||||||
[ok ] daemon responding
|
|
||||||
[ok ] mounted at: /home/you/ProtonDrive
|
|
||||||
[ok ] queued writes: none
|
|
||||||
|
|
||||||
No problems found.
|
|
||||||
```
|
|
||||||
|
|
||||||
It exits non-zero if any check fails, so it works in a health-check script.
|
|
||||||
|
|
||||||
For the local metadata database and content cache:
|
|
||||||
|
|
||||||
| Command | What it does |
|
|
||||||
|---|---|
|
|---|---|
|
||||||
| `pdfs cache inspect` | Database size, reclaimable space, per-table row counts, cache usage against budget |
|
| Debian, Ubuntu 24.04 or newer | `.deb` from the [latest release](https://github.com/narrrl/proton-drive-linux/releases/latest) |
|
||||||
| `pdfs cache inspect --deep` | Also runs SQLite's integrity check — reads every page, so it is slow on a large database |
|
| Fedora 44 or newer | `.rpm` from the [latest release](https://github.com/narrrl/proton-drive-linux/releases/latest) |
|
||||||
| `pdfs cache vacuum` | Checkpoints the write-ahead log and compacts the database |
|
| Arch Linux | [`proton-drive-for-linux`](https://aur.archlinux.org/packages/proton-drive-for-linux) (also `-bin` and `-git`) in the AUR |
|
||||||
| `pdfs cache clear` | Deletes cached file content, keeping pinned files |
|
| Anything else | [Build from source](docs/INSTALL.md#build-from-source) |
|
||||||
|
|
||||||
`vacuum` takes a write lock for its duration and needs room for a second copy of
|
```bash
|
||||||
the database while it runs, so it is a deliberate operation rather than
|
sudo apt install ./proton-drive-linux_*.deb # Debian, Ubuntu
|
||||||
something the daemon does on a timer.
|
sudo dnf install ./proton-drive-linux-*.rpm # Fedora
|
||||||
|
paru -S proton-drive-for-linux # Arch
|
||||||
Before a release, run the real-kernel acceptance suite against a dedicated test
|
|
||||||
account and mount. See [`docs/TESTING.md`](docs/TESTING.md); it includes optional
|
|
||||||
two-mount convergence checks and confines destructive operations to a fresh
|
|
||||||
test directory.
|
|
||||||
|
|
||||||
## Scripting (`--json`)
|
|
||||||
|
|
||||||
Query commands accept a global `--json` flag and emit machine-readable output:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ pdfs --json sync list | jq '.items[] | select(.state != "idle") | .local_path'
|
|
||||||
$ pdfs --json cache inspect | jq '.db_reclaimable_bytes'
|
|
||||||
$ pdfs --json status | jq -r '.mount.mountpoint'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Supported on `status`, `ls`, `pins`, `sync list`, `devices list`, `transfers`,
|
Then open **Proton Drive** from your applications menu and sign in. The app starts the background
|
||||||
`activity`, and `cache inspect`. Commands that perform an action keep their
|
service, and your files appear in `~/ProtonDrive`.
|
||||||
human output — a script that needs to know whether one worked has the exit code.
|
|
||||||
|
|
||||||
Two things worth relying on:
|
Signing in from a terminal instead? `pdfs login` stores the session but does not start the
|
||||||
|
service. Enable it once:
|
||||||
|
|
||||||
- **The payload is unwrapped.** Output is `{"items": […]}`, not the daemon's
|
```bash
|
||||||
internal `{"SyncFolders": {"items": […]}}`, so scripts never name an internal
|
pdfs login
|
||||||
variant.
|
systemctl --user enable --now proton-drive.service
|
||||||
- **Errors still fail.** A daemon-side error prints its JSON body (with a
|
pdfs status
|
||||||
machine-readable `kind`) on stdout *and* exits non-zero, so `set -e` and
|
```
|
||||||
`if pdfs …` behave as expected rather than treating a failed lookup as
|
|
||||||
success.
|
|
||||||
|
|
||||||
## Performance & Caching
|
[docs/INSTALL.md](docs/INSTALL.md) has the requirements, optional extras (tray on GNOME, RAW and
|
||||||
|
video thumbnails), headless use and uninstalling.
|
||||||
|
|
||||||
The client includes several optimizations designed for high efficiency, a low memory footprint, and a responsive user experience:
|
## Documentation
|
||||||
|
|
||||||
- **On-Demand Block Cache**: Files are read in fixed 4 MiB blocks. For unpinned files, the client fetches and caches only the blocks containing the requested byte ranges. This enables fast sequential and sparse reads (e.g. media streaming or metadata scanning) without downloading entire files.
|
| | |
|
||||||
- **Disk-Backed Writes**: Large file writes are staged on disk in temporary scratch files (rather than fully buffered in RAM) and track modified byte intervals. Only the unedited remote segments are fetched at commit time, keeping memory usage minimal.
|
|---|---|
|
||||||
- **Non-Blocking GTK4 Loop**: To prevent UI freezes, all synchronous D-Bus credential checks, control socket requests, and cache usage calculations are offloaded to background worker threads or fetched asynchronously.
|
| [User guide](docs/USER_GUIDE.md) | Everything the app, tray and launcher do |
|
||||||
- **Flicker-Free UI Rendering**: The GTK4 application performs differential rendering of the pins list, only modifying list rows when the list content changes, preserving the user's scroll position and widget focus.
|
| [Command-line reference](docs/CLI.md) | Every `pdfs` command and scripting with `--json` |
|
||||||
- **Durable Staging**: Scratch metadata and staged writes are synced and atomically published before an upload is acknowledged locally. Rapid revisions supersede pending work transactionally.
|
| [Configuration](docs/CONFIGURATION.md) | `config.json`, file locations, environment variables, the service |
|
||||||
|
| [Troubleshooting](docs/TROUBLESHOOTING.md) | Diagnosing problems and fixing common ones |
|
||||||
## Filesystem Safety
|
| [Recovering on a new computer](docs/RECOVERY.md) | Getting your synced folders back after a reinstall or a lost machine |
|
||||||
|
| [Changelog](docs/CHANGELOG.md) | What changed in each release |
|
||||||
Version 1.0 strengthens the boundaries around local-only data and destructive reconciliation:
|
|
||||||
|
|
||||||
- Truncate composes with queued revisions, preserving the authoritative prefix and correct zero-filled growth.
|
|
||||||
- Combined move-and-rename operations are queued durably and tolerate partially completed remote state.
|
|
||||||
- Session tombstones prevent successfully unlinked entries from reappearing through eventually consistent listings.
|
|
||||||
- Names are valid UTF-8 and no longer than Linux's 255-byte component limit.
|
|
||||||
- Incomplete mirror scans are non-destructive, and total-wipe protection covers every non-empty baseline.
|
|
||||||
- Failed conflict preservation, local deletion, or staging publication does not settle a new baseline or discard the only local copy.
|
|
||||||
- State, cache, and config directories must be real, current-user-owned `0700` directories; control sockets are `0600`.
|
|
||||||
|
|
||||||
|
For contributors: [development guide](docs/DEVELOPMENT.md), [architecture](docs/ARCHITECTURE.md),
|
||||||
|
[testing](docs/TESTING.md), [translating](docs/TRANSLATING.md), [roadmap](docs/ROADMAP.md) and the
|
||||||
|
[bug ledger](docs/BUGS.md). The [documentation index](docs/README.md) lists everything.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
### GUI Application & Launcher
|
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" width="50%"><img src="images/login.png" alt="Login Screen" width="100%"><br><sub><b>Login Screen</b></sub></td>
|
<td align="center" width="50%"><img src="images/files.png" alt="My files" width="100%"><br><sub><b>My files</b></sub></td>
|
||||||
<td align="center" width="50%"><img src="images/files.png" alt="Files Browser" width="100%"><br><sub><b>Files Browser</b></sub></td>
|
<td align="center" width="50%"><img src="images/photos.png" alt="Photos timeline" width="100%"><br><sub><b>Photos</b></sub></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" width="50%"><img src="images/shared.png" alt="Shared Links" width="100%"><br><sub><b>Shared Links</b></sub></td>
|
<td align="center" width="50%"><img src="images/photos_map.png" alt="Photos places map" width="100%"><br><sub><b>Places</b></sub></td>
|
||||||
<td align="center" width="50%"><img src="images/shared_with_me.png" alt="Shared with me" width="100%"><br><sub><b>Shared with me</b></sub></td>
|
<td align="center" width="50%"><img src="images/computers.png" alt="Computers" width="100%"><br><sub><b>Computers</b></sub></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" width="50%"><img src="images/restore_devices.png" alt="Local Backups (Computers)" width="100%"><br><sub><b>Local Backups (Computers)</b></sub></td>
|
<td align="center" width="50%"><img src="images/sync.png" alt="Sync folders" width="100%"><br><sub><b>Sync</b></sub></td>
|
||||||
<td align="center" width="50%"><img src="images/photos.png" alt="Photos Timeline" width="100%"><br><sub><b>Photos Timeline</b></sub></td>
|
<td align="center" width="50%"><img src="images/conflicts.png" alt="Sync history showing resolved conflicts" width="100%"><br><sub><b>Sync history</b></sub></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" width="50%"><img src="images/prompt_gtk.png" alt="Search Launcher Prompt (GTK)" width="100%"><br><sub><b>Search Launcher Prompt (GTK)</b></sub></td>
|
<td align="center" width="50%"><img src="images/prompt_gtk.png" alt="Search launcher" width="100%"><br><sub><b>Search launcher</b></sub></td>
|
||||||
<td align="center" width="50%"><img src="images/prompt_fzf.png" alt="Search Launcher Prompt (fzf)" width="100%"><br><sub><b>Search Launcher Prompt (fzf)</b></sub></td>
|
<td align="center" width="50%"><img src="images/prompt_fzf.png" alt="Search launcher in fzf" width="100%"><br><sub><b>Search launcher in fzf</b></sub></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" width="50%"><img src="images/activity.png" alt="Activity" width="100%"><br><sub><b>Activity</b></sub></td>
|
<td align="center" width="50%"><img src="images/login.png" alt="Sign in" width="100%"><br><sub><b>Sign in</b></sub></td>
|
||||||
<td align="center" width="50%"><img src="images/settings.png" alt="Settings" width="100%"><br><sub><b>Settings</b></sub></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
---
|
## How it works
|
||||||
|
|
||||||
## Prerequisites
|
A background service (`proton-drive.service`, the `pdfs daemon` command) holds your session,
|
||||||
|
mounts `~/ProtonDrive` through FUSE, runs the upload queue and the sync engine, and follows
|
||||||
|
Proton's event stream for changes made elsewhere. The app, the tray, the launcher and the CLI are
|
||||||
|
clients of that service over a local socket. Everything is end-to-end encrypted with your Proton
|
||||||
|
keys before it leaves the machine, through a Rust implementation of Proton's Drive SDK.
|
||||||
|
|
||||||
To compile the application from source or run the built binaries, ensure you have the following system libraries installed on your distribution:
|
Files you open are decrypted into a local cache so programs can read them. What that means for
|
||||||
|
the security of the machine is spelled out in the
|
||||||
### Ubuntu / Debian (24.04+)
|
[threat model](docs/ARCHITECTURE.md#8-threat-model-what-this-client-writes-to-disk-in-plaintext).
|
||||||
```bash
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y \
|
|
||||||
pkg-config \
|
|
||||||
libfuse3-dev \
|
|
||||||
libgtk-4-dev \
|
|
||||||
libadwaita-1-dev \
|
|
||||||
libwebkitgtk-6.0-dev \
|
|
||||||
libsecret-1-dev \
|
|
||||||
libdbus-1-dev \
|
|
||||||
libimage-exiftool-perl
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arch Linux
|
|
||||||
```bash
|
|
||||||
sudo pacman -S --needed pkgconf fuse3 gtk4 libadwaita libsecret dbus webkitgtk-6.0 perl-image-exiftool
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fedora (44+)
|
|
||||||
```bash
|
|
||||||
sudo dnf install -y \
|
|
||||||
pkgconf-pkg-config fuse3-devel gtk4-devel libadwaita-devel \
|
|
||||||
webkitgtk6.0-devel libsecret-devel dbus-devel glib2-devel \
|
|
||||||
perl-Image-ExifTool cargo rust
|
|
||||||
```
|
|
||||||
|
|
||||||
Runtime extras (pick your desktop):
|
|
||||||
```bash
|
|
||||||
# GNOME — keyring + tray (AppIndicator)
|
|
||||||
sudo dnf install -y gnome-keyring gnome-shell-extension-appindicator xdg-utils
|
|
||||||
|
|
||||||
# KDE Plasma — KWallet (Secret Service); tray works via built-in SNI
|
|
||||||
sudo dnf install -y kwallet xdg-utils
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Building from Source
|
|
||||||
|
|
||||||
Ensure you have Rust and Cargo installed (minimum supported Rust version is 1.96).
|
|
||||||
|
|
||||||
1. Clone the repository and navigate into the project directory:
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/narrrl/proton-drive-linux.git
|
|
||||||
cd proton-drive-linux
|
|
||||||
```
|
|
||||||
2. Build the workspace in release mode:
|
|
||||||
```bash
|
|
||||||
cargo build --release --locked
|
|
||||||
```
|
|
||||||
|
|
||||||
The compiled binaries will be available under `target/release/`:
|
|
||||||
- `pdfs`: The CLI utility.
|
|
||||||
- `pdfs-app`: The GTK4 application.
|
|
||||||
- `pdfs-tray`: The tray status notifier.
|
|
||||||
- `pdfs-prompt`: The launcher prompt for quick HUD search.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Installation & Packages
|
|
||||||
|
|
||||||
### 1. Debian / Ubuntu (.deb)
|
|
||||||
Install the debian package via `dpkg` or `apt`:
|
|
||||||
```bash
|
|
||||||
sudo apt install ./proton-drive-linux_*.deb
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Arch Linux (AUR)
|
|
||||||
Three AUR packages cover the usual choices; they conflict with each other, so
|
|
||||||
install one:
|
|
||||||
|
|
||||||
| Package | Contents |
|
|
||||||
| --- | --- |
|
|
||||||
| [`proton-drive-for-linux`](https://aur.archlinux.org/packages/proton-drive-for-linux) | builds the latest tagged release from source |
|
|
||||||
| [`proton-drive-for-linux-git`](https://aur.archlinux.org/packages/proton-drive-for-linux-git) | builds `main` from git |
|
|
||||||
| [`proton-drive-for-linux-bin`](https://aur.archlinux.org/packages/proton-drive-for-linux-bin) | installs the release binaries, no compile |
|
|
||||||
|
|
||||||
```bash
|
|
||||||
paru -S proton-drive-for-linux # or any AUR helper
|
|
||||||
```
|
|
||||||
|
|
||||||
The package sources live in `packaging/aur/`; see
|
|
||||||
[`packaging/aur/README.md`](packaging/aur/README.md) for how they are updated and
|
|
||||||
published. A local `PKGBUILD` that compiles the working tree in place is still
|
|
||||||
available for development builds:
|
|
||||||
```bash
|
|
||||||
cd packaging && makepkg -fi
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Fedora (local RPM)
|
|
||||||
A local `.spec` is available under `packaging/`. From the repository root:
|
|
||||||
```bash
|
|
||||||
sudo dnf install -y rpm-build
|
|
||||||
rpmbuild -bb packaging/proton-drive-linux.spec \
|
|
||||||
--define "git_dir $PWD" \
|
|
||||||
--define "_rpmdir $PWD/packaging/out" \
|
|
||||||
--define "_builddir $PWD/packaging/build" \
|
|
||||||
--define "_sourcedir $PWD" \
|
|
||||||
--define "_specdir $PWD/packaging" \
|
|
||||||
--define "_srcrpmdir $PWD/packaging/out"
|
|
||||||
sudo dnf install packaging/out/x86_64/proton-drive-linux-*.rpm
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Automated Releases (CI/CD)
|
|
||||||
|
|
||||||
This project has a GitHub Actions CI workflow configured under `.github/workflows/release.yml`.
|
|
||||||
|
|
||||||
### How it works:
|
|
||||||
1. **Triggers**:
|
|
||||||
- Pushing a git tag matching `v*` (e.g. `git tag v0.1.0 && git push origin v0.1.0`).
|
|
||||||
- Manual runs via the **Actions** tab in GitHub (**workflow_dispatch**).
|
|
||||||
2. **Quality Gates and Build Process**:
|
|
||||||
- For tagged builds, verifies that the tag, workspace version, and `packaging/PKGBUILD` version agree.
|
|
||||||
- Runs `cargo fmt`, Clippy with warnings denied, the locked workspace test suite, and the account-free FUSE acceptance contract.
|
|
||||||
- Spawns an Ubuntu runner and installs GTK4, Libadwaita, FUSE3, and Secret Service packages.
|
|
||||||
- Sets up the Rust compiler and caches build targets to speed up runs.
|
|
||||||
- Compiles the workspace members in release mode.
|
|
||||||
3. **Artifact Packaging**:
|
|
||||||
- Generates a `.tar.gz` containing the raw binaries (`pdfs`, `pdfs-app`, `pdfs-tray`, `pdfs-prompt`).
|
|
||||||
- Packs them into Debian (`.deb`) and Fedora (`.rpm`) packages.
|
|
||||||
- Includes the systemd user service and tray autostart entry in the Debian package.
|
|
||||||
4. **Publishing**:
|
|
||||||
- Creates a GitHub Release matching the pushed tag and uploads the `.deb`, `.rpm`, and `.tar.gz` packages as release assets.
|
|
||||||
- For manual runs, compiles and exposes the packages as workflow run artifacts for testing.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Bug reports, packaging fixes, and patches are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for
|
Bug reports, translations and patches are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the
|
||||||
the workflow and the quality gates CI enforces, and [SECURITY.md](SECURITY.md) for reporting a
|
workflow and the checks CI runs, and [SECURITY.md](SECURITY.md) to report a vulnerability
|
||||||
vulnerability privately.
|
privately.
|
||||||
|
|
||||||
The app, tray and search prompt are available in English, German, Spanish, French, Italian, Dutch,
|
|
||||||
Polish and Brazilian Portuguese. [docs/TRANSLATING.md](docs/TRANSLATING.md) explains how to improve
|
|
||||||
a translation or add a language.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Released under the [MIT License](LICENSE).
|
Released under the [MIT License](LICENSE).
|
||||||
|
|
||||||
|
The Places map uses [GeoNames](https://www.geonames.org/) data (CC BY 4.0) and
|
||||||
|
[Natural Earth](https://www.naturalearthdata.com/) outlines (public domain). The optional street
|
||||||
|
map comes from [OpenFreeMap](https://openfreemap.org/), © [OpenStreetMap](https://www.openstreetmap.org/copyright)
|
||||||
|
contributors.
|
||||||
|
|
||||||
Proton Drive is a service of Proton AG. This project is an independent client and carries no
|
Proton Drive is a service of Proton AG. This project is an independent client and carries no
|
||||||
affiliation with or endorsement from Proton AG.
|
affiliation with or endorsement from Proton AG.
|
||||||
@@ -30,5 +30,10 @@ the interesting boundaries are:
|
|||||||
- **Cryptography.** Key derivation, signature handling, and anything that could publish plaintext
|
- **Cryptography.** Key derivation, signature handling, and anything that could publish plaintext
|
||||||
or the wrong key material to Proton.
|
or the wrong key material to Proton.
|
||||||
|
|
||||||
|
What the client deliberately keeps in plaintext on disk, and why, is documented in the
|
||||||
|
[threat model](docs/ARCHITECTURE.md#8-threat-model-what-this-client-writes-to-disk-in-plaintext).
|
||||||
|
Behaviour described there is known and not a vulnerability by itself; a way to make it worse
|
||||||
|
than described is.
|
||||||
|
|
||||||
Out of scope: vulnerabilities in Proton's own service (report those to Proton AG), and issues that
|
Out of scope: vulnerabilities in Proton's own service (report those to Proton AG), and issues that
|
||||||
require an attacker who already has your user account on the machine.
|
require an attacker who already has your user account on the machine.
|
||||||
@@ -27,5 +27,6 @@ fn main() {
|
|||||||
println!("cargo:rerun-if-changed=resources/icons");
|
println!("cargo:rerun-if-changed=resources/icons");
|
||||||
println!("cargo:rerun-if-changed=resources/style.css");
|
println!("cargo:rerun-if-changed=resources/style.css");
|
||||||
println!("cargo:rerun-if-changed=resources/proton-theme.css");
|
println!("cargo:rerun-if-changed=resources/proton-theme.css");
|
||||||
|
println!("cargo:rerun-if-changed=resources/prompt.css");
|
||||||
println!("cargo:rerun-if-changed=../../packaging/io.narl.proton-drive-linux.svg");
|
println!("cargo:rerun-if-changed=../../packaging/io.narl.proton-drive-linux.svg");
|
||||||
}
|
}
|
||||||
@@ -33,5 +33,7 @@
|
|||||||
<gresource prefix="/de/nils/protondrivelinux">
|
<gresource prefix="/de/nils/protondrivelinux">
|
||||||
<file>style.css</file>
|
<file>style.css</file>
|
||||||
<file>proton-theme.css</file>
|
<file>proton-theme.css</file>
|
||||||
|
<!-- The search launcher's own sheet, over the same palette. -->
|
||||||
|
<file>prompt.css</file>
|
||||||
</gresource>
|
</gresource>
|
||||||
</gresources>
|
</gresources>
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
/* The search launcher (pdfs-prompt). Colours come from the libadwaita
|
||||||
|
palette, so the launcher follows the system accent, light/dark and high
|
||||||
|
contrast like the app; the opt-in Proton theme recolours both alike. */
|
||||||
|
|
||||||
|
window.launcher-window {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.launcher-card {
|
||||||
|
background-color: var(--view-bg-color);
|
||||||
|
color: var(--view-fg-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 14px;
|
||||||
|
box-shadow: 0 16px 48px rgb(0 0 0 / 35%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
padding: 14px 18px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
-gtk-icon-size: 20px;
|
||||||
|
color: color-mix(in srgb, currentColor 55%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-entry {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
padding: 2px 0;
|
||||||
|
font-size: 1.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-hint {
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: color-mix(in srgb, currentColor 8%, transparent);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip-row {
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip {
|
||||||
|
padding: 4px 14px;
|
||||||
|
min-height: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: none;
|
||||||
|
font-size: 0.86rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip:hover {
|
||||||
|
background-color: color-mix(in srgb, currentColor 6%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip:checked {
|
||||||
|
background-color: var(--accent-bg-color);
|
||||||
|
color: var(--accent-fg-color);
|
||||||
|
border-color: var(--accent-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- results ------------------------------------------------------------ */
|
||||||
|
|
||||||
|
.results {
|
||||||
|
padding: 2px 8px 10px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
padding: 12px 10px 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-icon {
|
||||||
|
-gtk-icon-size: 14px;
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-list {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-row {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-row:hover {
|
||||||
|
background-color: color-mix(in srgb, currentColor 5%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A soft tint rather than a solid accent bar: the text keeps its colour, so
|
||||||
|
the highlighted match stays readable on the selected row. */
|
||||||
|
.result-row:selected,
|
||||||
|
.result-row:selected:hover {
|
||||||
|
background-color: color-mix(in srgb, var(--accent-bg-color) 18%, transparent);
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-name {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-location,
|
||||||
|
.result-meta {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-row:selected .result-location,
|
||||||
|
.result-row:selected .result-meta {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb {
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-badge,
|
||||||
|
.preview-badge {
|
||||||
|
-gtk-icon-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-pinned {
|
||||||
|
color: var(--success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-cached {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-cloud,
|
||||||
|
.badge-local {
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- preview ------------------------------------------------------------ */
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
padding: 16px;
|
||||||
|
background-color: color-mix(in srgb, currentColor 3%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-thumb {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-thumb .thumb {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-name {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-kind {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
opacity: 0.6;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-key {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-value {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-actions {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-action {
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-shortcut {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-feedback {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-empty {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- footer ------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-text {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
statuspage.compact > scrolledwindow > viewport > box {
|
||||||
|
margin: 24px 12px;
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ pub(crate) mod events;
|
|||||||
pub(crate) mod i18n;
|
pub(crate) mod i18n;
|
||||||
pub(crate) mod pages;
|
pub(crate) mod pages;
|
||||||
pub(crate) mod reload;
|
pub(crate) mod reload;
|
||||||
|
#[path = "../theme.rs"]
|
||||||
|
pub(crate) mod theme;
|
||||||
pub(crate) mod widgets;
|
pub(crate) mod widgets;
|
||||||
|
|
||||||
use compat::*;
|
use compat::*;
|
||||||
@@ -30,6 +32,7 @@ use pages::takeout::*;
|
|||||||
use pages::trash::*;
|
use pages::trash::*;
|
||||||
use pages::verify::*;
|
use pages::verify::*;
|
||||||
use reload::{LoadTicket, Loader, replace_items};
|
use reload::{LoadTicket, Loader, replace_items};
|
||||||
|
use theme::{load_resources, proton_theme_active, set_proton_theme};
|
||||||
use widgets::compat::*;
|
use widgets::compat::*;
|
||||||
use widgets::details::*;
|
use widgets::details::*;
|
||||||
use widgets::file_list::*;
|
use widgets::file_list::*;
|
||||||
@@ -210,7 +213,7 @@ fn main() -> glib::ExitCode {
|
|||||||
.flags(gio::ApplicationFlags::HANDLES_COMMAND_LINE)
|
.flags(gio::ApplicationFlags::HANDLES_COMMAND_LINE)
|
||||||
.build();
|
.build();
|
||||||
app.connect_startup(|_| {
|
app.connect_startup(|_| {
|
||||||
load_proton_theme();
|
load_resources("/de/nils/protondrivelinux/style.css");
|
||||||
if let Ok(dirs) = AppDirs::new() {
|
if let Ok(dirs) = AppDirs::new() {
|
||||||
set_proton_theme(dirs.load_config().proton_theme.unwrap_or(false));
|
set_proton_theme(dirs.load_config().proton_theme.unwrap_or(false));
|
||||||
}
|
}
|
||||||
@@ -287,157 +290,6 @@ pub(crate) fn spawn_tray() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register the bundled GResources (custom icons, the stylesheet) and load the
|
|
||||||
/// app stylesheet. The Proton theme is a separate provider, applied by
|
|
||||||
/// [`set_proton_theme`] from the saved preference.
|
|
||||||
fn load_proton_theme() {
|
|
||||||
let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/pdfs.gresource"));
|
|
||||||
let resource_data = glib::Bytes::from_static(bytes);
|
|
||||||
if let Ok(resource) = gio::Resource::from_data(&resource_data) {
|
|
||||||
gio::resources_register(&resource);
|
|
||||||
} else {
|
|
||||||
tracing::error!("failed to load gresource bundle");
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(display) = gtk4::gdk::Display::default() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let provider = gtk4::CssProvider::new();
|
|
||||||
provider.load_from_resource("/de/nils/protondrivelinux/style.css");
|
|
||||||
// Above the user's own gtk.css, which often imports a whole theme: its
|
|
||||||
// `button { padding: 6px 10px; min-width: 16px }` would otherwise pad every
|
|
||||||
// photo tile and push the grid past the window. Every rule in the sheet is
|
|
||||||
// scoped to the app's own classes, so nothing else the user styled changes.
|
|
||||||
// One above the Proton theme too, so the app's rules refine it.
|
|
||||||
gtk4::style_context_add_provider_for_display(
|
|
||||||
&display,
|
|
||||||
&provider,
|
|
||||||
gtk4::STYLE_PROVIDER_PRIORITY_USER + 2,
|
|
||||||
);
|
|
||||||
gtk4::IconTheme::for_display(&display).add_resource_path("/de/nils/protondrivelinux/icons");
|
|
||||||
}
|
|
||||||
|
|
||||||
thread_local! {
|
|
||||||
/// The Proton theme provider while it is installed. Kept so turning the
|
|
||||||
/// preference off can remove exactly what turning it on added.
|
|
||||||
static PROTON_THEME: RefCell<Option<gtk4::CssProvider>> = const { RefCell::new(None) };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the Proton theme is on.
|
|
||||||
fn proton_theme_active() -> bool {
|
|
||||||
PROTON_THEME.with(|slot| slot.borrow().is_some())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// libadwaita's own stylesheet, bundled in the library.
|
|
||||||
const ADWAITA_STYLESHEET: &str = "/org/gnome/Adwaita/styles/gtk.css";
|
|
||||||
|
|
||||||
/// The Proton palette laid over it, from our own bundle.
|
|
||||||
const PROTON_PALETTE: &str = "/de/nils/protondrivelinux/proton-theme.css";
|
|
||||||
|
|
||||||
/// Make the theme provider's `@media (prefers-color-scheme)` and
|
|
||||||
/// `(prefers-contrast)` rules match what libadwaita shows, now and whenever it
|
|
||||||
/// changes. A provider evaluates those itself, and a new one assumes light.
|
|
||||||
///
|
|
||||||
/// The properties are set by name because they only exist since GTK 4.20, and
|
|
||||||
/// the app still runs on older GTK, whose libadwaita has no such rules anyway.
|
|
||||||
fn follow_style_manager(provider: >k4::CssProvider) {
|
|
||||||
fn set_enum(provider: >k4::CssProvider, property: &str, nick: &str) {
|
|
||||||
let Some(pspec) = provider.find_property(property) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if let Some(value) = glib::EnumClass::with_type(pspec.value_type())
|
|
||||||
.and_then(|class| class.to_value_by_nick(nick))
|
|
||||||
{
|
|
||||||
provider.set_property_from_value(property, &value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn apply(provider: >k4::CssProvider, manager: &adw::StyleManager) {
|
|
||||||
let scheme = if manager.is_dark() { "dark" } else { "light" };
|
|
||||||
set_enum(provider, "prefers-color-scheme", scheme);
|
|
||||||
let contrast = if manager.is_high_contrast() {
|
|
||||||
"more"
|
|
||||||
} else {
|
|
||||||
"no-preference"
|
|
||||||
};
|
|
||||||
set_enum(provider, "prefers-contrast", contrast);
|
|
||||||
}
|
|
||||||
|
|
||||||
let manager = adw::StyleManager::default();
|
|
||||||
apply(provider, &manager);
|
|
||||||
// Weak, so a provider the preference removed isn't kept alive; its
|
|
||||||
// handlers then do nothing.
|
|
||||||
let weak = provider.downgrade();
|
|
||||||
let weak_dark = weak.clone();
|
|
||||||
manager.connect_dark_notify(move |manager| {
|
|
||||||
if let Some(provider) = weak_dark.upgrade() {
|
|
||||||
apply(&provider, manager);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
manager.connect_high_contrast_notify(move |manager| {
|
|
||||||
if let Some(provider) = weak.upgrade() {
|
|
||||||
apply(&provider, manager);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Paint the app in the Proton web apps' colours (`on`) or follow the system
|
|
||||||
/// theme and accent colour.
|
|
||||||
///
|
|
||||||
/// Setting libadwaita's colour variables alone isn't enough: a GTK theme the
|
|
||||||
/// user's `gtk.css` imports (Catppuccin, Fluent, ...) sits above the app and
|
|
||||||
/// hard-codes its own accent into hundreds of rules. So the theme provider,
|
|
||||||
/// one above the user's CSS, first resets every property, then re-applies
|
|
||||||
/// libadwaita's whole stylesheet and finally the Proton palette, which leaves
|
|
||||||
/// nothing of the user's theme inside the app.
|
|
||||||
fn set_proton_theme(on: bool) {
|
|
||||||
let Some(display) = gtk4::gdk::Display::default() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
PROTON_THEME.with(|slot| {
|
|
||||||
let mut slot = slot.borrow_mut();
|
|
||||||
match (on, slot.as_ref()) {
|
|
||||||
(true, None) => {
|
|
||||||
let stylesheet = |path| {
|
|
||||||
gio::resources_lookup_data(path, gio::ResourceLookupFlags::NONE)
|
|
||||||
.map(|bytes| String::from_utf8_lossy(&bytes).into_owned())
|
|
||||||
};
|
|
||||||
let palette = stylesheet(PROTON_PALETTE).unwrap_or_default();
|
|
||||||
let css = match stylesheet(ADWAITA_STYLESHEET) {
|
|
||||||
// Its images are named relative to the sheet, which a
|
|
||||||
// string loaded here doesn't have.
|
|
||||||
Ok(adwaita) => format!(
|
|
||||||
"* {{ all: unset; }}\n{}\n{palette}",
|
|
||||||
adwaita.replace(
|
|
||||||
"url(\"assets/",
|
|
||||||
"url(\"resource:///org/gnome/Adwaita/styles/assets/"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
// Still recolours everything that follows libadwaita's
|
|
||||||
// variables; only a custom theme's own rules show through.
|
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!("libadwaita stylesheet not found, only recolouring: {e}");
|
|
||||||
palette
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let provider = gtk4::CssProvider::new();
|
|
||||||
follow_style_manager(&provider);
|
|
||||||
provider.load_from_string(&css);
|
|
||||||
gtk4::style_context_add_provider_for_display(
|
|
||||||
&display,
|
|
||||||
&provider,
|
|
||||||
gtk4::STYLE_PROVIDER_PRIORITY_USER + 1,
|
|
||||||
);
|
|
||||||
*slot = Some(provider);
|
|
||||||
}
|
|
||||||
(false, Some(provider)) => {
|
|
||||||
gtk4::style_context_remove_provider_for_display(&display, provider);
|
|
||||||
*slot = None;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build the application window, wire the two pages, kick off the refresh loop,
|
/// Build the application window, wire the two pages, kick off the refresh loop,
|
||||||
/// and present it.
|
/// and present it.
|
||||||
fn build_window(app: &adw::Application) {
|
fn build_window(app: &adw::Application) {
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
//! Match highlighting: which characters of a result name the query matched,
|
||||||
|
//! as Pango markup with those characters in bold.
|
||||||
|
//!
|
||||||
|
//! Each query term is looked for as a substring of the name first, and as a
|
||||||
|
//! subsequence ("rpt" in "report") when that fails, which is roughly how the
|
||||||
|
//! daemon's ranking treats a term too. Case is folded one character at a
|
||||||
|
//! time, so every range stays on a character boundary of the original name,
|
||||||
|
//! whatever the script.
|
||||||
|
|
||||||
|
use gtk4::glib;
|
||||||
|
|
||||||
|
/// One character of a name: its byte range and its case-folded form.
|
||||||
|
struct Folded {
|
||||||
|
start: usize,
|
||||||
|
end: usize,
|
||||||
|
folded: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fold(text: &str) -> Vec<Folded> {
|
||||||
|
text.char_indices()
|
||||||
|
.map(|(start, c)| Folded {
|
||||||
|
start,
|
||||||
|
end: start + c.len_utf8(),
|
||||||
|
folded: c.to_lowercase().collect(),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The byte ranges of `name` that `query` matched, sorted and merged.
|
||||||
|
pub(crate) fn ranges(name: &str, query: &str) -> Vec<(usize, usize)> {
|
||||||
|
let chars = fold(name);
|
||||||
|
let mut found: Vec<(usize, usize)> = Vec::new();
|
||||||
|
for term in query.split_whitespace() {
|
||||||
|
let term: Vec<String> = fold(term).into_iter().map(|c| c.folded).collect();
|
||||||
|
if let Some(at) = substring(&chars, &term) {
|
||||||
|
found.push((chars[at].start, chars[at + term.len() - 1].end));
|
||||||
|
} else if let Some(hits) = subsequence(&chars, &term) {
|
||||||
|
found.extend(hits.into_iter().map(|at| (chars[at].start, chars[at].end)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
found.sort_unstable();
|
||||||
|
let mut merged: Vec<(usize, usize)> = Vec::with_capacity(found.len());
|
||||||
|
for (start, end) in found {
|
||||||
|
match merged.last_mut() {
|
||||||
|
Some(last) if start <= last.1 => last.1 = last.1.max(end),
|
||||||
|
_ => merged.push((start, end)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
merged
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where `term` first occurs in `chars`, as a character index.
|
||||||
|
fn substring(chars: &[Folded], term: &[String]) -> Option<usize> {
|
||||||
|
if term.is_empty() || term.len() > chars.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
(0..=chars.len() - term.len()).find(|&at| {
|
||||||
|
chars[at..at + term.len()]
|
||||||
|
.iter()
|
||||||
|
.zip(term)
|
||||||
|
.all(|(c, t)| c.folded == *t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The character indices of the leftmost match of `term` as a subsequence,
|
||||||
|
/// or `None` when not every character of it can be found in order.
|
||||||
|
fn subsequence(chars: &[Folded], term: &[String]) -> Option<Vec<usize>> {
|
||||||
|
if term.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut hits = Vec::with_capacity(term.len());
|
||||||
|
let mut next = 0;
|
||||||
|
for wanted in term {
|
||||||
|
let at = (next..chars.len()).find(|&i| chars[i].folded == *wanted)?;
|
||||||
|
hits.push(at);
|
||||||
|
next = at + 1;
|
||||||
|
}
|
||||||
|
Some(hits)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `name` as Pango markup, escaped, with the matched characters in bold.
|
||||||
|
pub(crate) fn markup(name: &str, query: &str) -> String {
|
||||||
|
let mut out = String::with_capacity(name.len() + 16);
|
||||||
|
let mut done = 0;
|
||||||
|
for (start, end) in ranges(name, query) {
|
||||||
|
out.push_str(&glib::markup_escape_text(&name[done..start]));
|
||||||
|
out.push_str("<b>");
|
||||||
|
out.push_str(&glib::markup_escape_text(&name[start..end]));
|
||||||
|
out.push_str("</b>");
|
||||||
|
done = end;
|
||||||
|
}
|
||||||
|
out.push_str(&glib::markup_escape_text(&name[done..]));
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_substring_match_is_one_range_in_any_case() {
|
||||||
|
assert_eq!(ranges("Tax Report.pdf", "REP"), vec![(4, 7)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_term_that_is_no_substring_falls_back_to_a_subsequence() {
|
||||||
|
// r, p and t of "report", not the "rt" pair at the end of a substring.
|
||||||
|
assert_eq!(ranges("report.txt", "rpt"), vec![(0, 1), (2, 3), (5, 6)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_term_with_no_match_highlights_nothing() {
|
||||||
|
assert!(ranges("report.txt", "xyz").is_empty());
|
||||||
|
assert!(ranges("report.txt", "").is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn several_terms_merge_into_sorted_ranges() {
|
||||||
|
assert_eq!(
|
||||||
|
ranges("tax return 2024", "2024 tax ret"),
|
||||||
|
vec![(0, 3), (4, 7), (11, 15)]
|
||||||
|
);
|
||||||
|
assert_eq!(ranges("abc", "ab bc"), vec![(0, 3)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_ascii_names_keep_ranges_on_character_boundaries() {
|
||||||
|
assert_eq!(ranges("Über Größe.odt", "über"), vec![(0, 5)]);
|
||||||
|
assert_eq!(ranges("Über Größe.odt", "GRÖ"), vec![(6, 10)]);
|
||||||
|
assert_eq!(markup("Über Größe.odt", "größe"), "Über <b>Größe</b>.odt");
|
||||||
|
assert_eq!(
|
||||||
|
markup("日本語のメモ.txt", "メモ"),
|
||||||
|
"日本語の<b>メモ</b>.txt"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn markup_escapes_the_name_inside_and_outside_matches() {
|
||||||
|
assert_eq!(markup("a<b>&c", "b"), "a<<b>b</b>>&c");
|
||||||
|
assert_eq!(markup("R&D.pdf", "r&d"), "<b>R&D</b>.pdf");
|
||||||
|
assert_eq!(markup("<none>", "zzz"), "<none>");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,363 @@
|
|||||||
|
//! Recent files and frecency.
|
||||||
|
//!
|
||||||
|
//! The launcher remembers what it opened in `prompt-history.json` under the
|
||||||
|
//! state directory. With an empty query those files come first, and while
|
||||||
|
//! searching a file opened often and lately gets a bonus on top of its match
|
||||||
|
//! score (see [`frecency_bonus`]).
|
||||||
|
//!
|
||||||
|
//! One writer thread owns the file: it loads it, applies every recorded visit
|
||||||
|
//! in order and writes the result back atomically. A visit recorded before the
|
||||||
|
//! first load finished simply waits in the channel, so it is merged into the
|
||||||
|
//! loaded list rather than overwriting it. The main thread only ever sees
|
||||||
|
//! snapshots.
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use gtk4::glib;
|
||||||
|
|
||||||
|
use crate::Hit;
|
||||||
|
|
||||||
|
/// How many files the history keeps. More than the launcher ever shows, so
|
||||||
|
/// frecency still knows a file that dropped off the recent list.
|
||||||
|
const MAX_VISITS: usize = 50;
|
||||||
|
|
||||||
|
/// The frecency bonus of one visit made just now.
|
||||||
|
const VISIT_BONUS: f64 = 1500.0;
|
||||||
|
|
||||||
|
/// The bonus halves every this many days without a visit.
|
||||||
|
const HALF_LIFE_DAYS: f64 = 14.0;
|
||||||
|
|
||||||
|
/// The largest frecency bonus. A name match is worth up to 20000 points, so
|
||||||
|
/// history breaks ties between similar matches but cannot beat a much better
|
||||||
|
/// one.
|
||||||
|
const MAX_BONUS: f64 = 6000.0;
|
||||||
|
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub(crate) struct Visit {
|
||||||
|
pub(crate) hit: Hit,
|
||||||
|
/// When the file was last opened, in epoch seconds.
|
||||||
|
pub(crate) last: i64,
|
||||||
|
pub(crate) count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The history bonus for a file opened `count` times, the last time
|
||||||
|
/// `age_secs` ago.
|
||||||
|
pub(crate) fn frecency_bonus(count: u32, age_secs: i64) -> i64 {
|
||||||
|
let age_days = age_secs.max(0) as f64 / 86_400.0;
|
||||||
|
let bonus = f64::from(count) * VISIT_BONUS * 0.5f64.powf(age_days / HALF_LIFE_DAYS);
|
||||||
|
bonus.min(MAX_BONUS) as i64
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Count one more visit of `hit` at `now`, newest first, and keep at most
|
||||||
|
/// [`MAX_VISITS`]. The stored hit is replaced, so its size, date and sync
|
||||||
|
/// state are the ones from the latest search.
|
||||||
|
fn record(visits: &mut Vec<Visit>, hit: Hit, now: i64) {
|
||||||
|
let key = hit.key();
|
||||||
|
let count = match visits.iter().position(|visit| visit.hit.key() == key) {
|
||||||
|
Some(at) => visits.remove(at).count.saturating_add(1),
|
||||||
|
None => 1,
|
||||||
|
};
|
||||||
|
visits.insert(
|
||||||
|
0,
|
||||||
|
Visit {
|
||||||
|
hit,
|
||||||
|
last: now,
|
||||||
|
count,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
visits.truncate(MAX_VISITS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drop local files that no longer exist. Drive files stay: checking them
|
||||||
|
/// would touch the mount, and a stale one only costs a row.
|
||||||
|
fn prune(visits: &mut Vec<Visit>) -> bool {
|
||||||
|
let before = visits.len();
|
||||||
|
visits.retain(|visit| match &visit.hit {
|
||||||
|
Hit::Local(hit) => Path::new(&hit.path).symlink_metadata().is_ok(),
|
||||||
|
Hit::Drive(_) => true,
|
||||||
|
});
|
||||||
|
visits.len() != before
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(path: &Path) -> Vec<Visit> {
|
||||||
|
match std::fs::read(path) {
|
||||||
|
Ok(bytes) => serde_json::from_slice(&bytes).unwrap_or_else(|e| {
|
||||||
|
tracing::warn!("ignoring unreadable {}: {e}", path.display());
|
||||||
|
Vec::new()
|
||||||
|
}),
|
||||||
|
Err(_) => Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the history through a temporary file and a rename, so a crash
|
||||||
|
/// mid-write leaves the old file rather than half a new one.
|
||||||
|
fn save(path: &Path, visits: &[Visit]) -> std::io::Result<()> {
|
||||||
|
let json = serde_json::to_vec(visits).map_err(std::io::Error::other)?;
|
||||||
|
if let Some(dir) = path.parent() {
|
||||||
|
std::fs::create_dir_all(dir)?;
|
||||||
|
}
|
||||||
|
let tmp = path.with_extension("json.tmp");
|
||||||
|
let mut file = std::fs::File::create(&tmp)?;
|
||||||
|
file.write_all(&json)?;
|
||||||
|
file.sync_all()?;
|
||||||
|
std::fs::rename(&tmp, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Command {
|
||||||
|
Record(Hit, i64),
|
||||||
|
Prune,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The writer thread's loop: load, then apply commands until the launcher
|
||||||
|
/// exits. `publish` receives the list after the load and after every change.
|
||||||
|
fn serve(path: &Path, commands: &async_channel::Receiver<Command>, publish: impl Fn(Vec<Visit>)) {
|
||||||
|
let mut visits = load(path);
|
||||||
|
let pruned = prune(&mut visits);
|
||||||
|
publish(visits.clone());
|
||||||
|
if pruned && let Err(e) = save(path, &visits) {
|
||||||
|
tracing::warn!("cannot write {}: {e}", path.display());
|
||||||
|
}
|
||||||
|
while let Ok(command) = commands.recv_blocking() {
|
||||||
|
let changed = match command {
|
||||||
|
Command::Record(hit, now) => {
|
||||||
|
record(&mut visits, hit, now);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Command::Prune => prune(&mut visits),
|
||||||
|
};
|
||||||
|
if changed {
|
||||||
|
if let Err(e) = save(path, &visits) {
|
||||||
|
tracing::warn!("cannot write {}: {e}", path.display());
|
||||||
|
}
|
||||||
|
publish(visits.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The main thread's view of the history.
|
||||||
|
pub(crate) struct History {
|
||||||
|
visits: Rc<RefCell<Vec<Visit>>>,
|
||||||
|
commands: async_channel::Sender<Command>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl History {
|
||||||
|
/// Start the writer thread. `changed` runs on the main thread whenever a
|
||||||
|
/// new snapshot arrives.
|
||||||
|
pub(crate) fn start(path: PathBuf, changed: impl Fn() + 'static) -> Self {
|
||||||
|
let (commands, inbox) = async_channel::unbounded();
|
||||||
|
let (snapshots, updates) = async_channel::unbounded::<Vec<Visit>>();
|
||||||
|
let spawned = std::thread::Builder::new()
|
||||||
|
.name("prompt-history".into())
|
||||||
|
.spawn(move || {
|
||||||
|
serve(&path, &inbox, |visits| {
|
||||||
|
let _ = snapshots.send_blocking(visits);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if let Err(e) = spawned {
|
||||||
|
tracing::warn!("cannot start the history writer: {e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let visits = Rc::new(RefCell::new(Vec::new()));
|
||||||
|
let shared = visits.clone();
|
||||||
|
glib::spawn_future_local(async move {
|
||||||
|
while let Ok(snapshot) = updates.recv().await {
|
||||||
|
*shared.borrow_mut() = snapshot;
|
||||||
|
changed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Self { visits, commands }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remember that `hit` was opened just now.
|
||||||
|
pub(crate) fn record(&self, hit: Hit) {
|
||||||
|
let _ = self.commands.try_send(Command::Record(hit, now()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Forget local files that were deleted since they were opened.
|
||||||
|
pub(crate) fn prune(&self) {
|
||||||
|
let _ = self.commands.try_send(Command::Prune);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The most recently opened files, newest first.
|
||||||
|
pub(crate) fn recent(&self, limit: usize) -> Vec<Hit> {
|
||||||
|
self.visits
|
||||||
|
.borrow()
|
||||||
|
.iter()
|
||||||
|
.take(limit)
|
||||||
|
.map(|visit| visit.hit.clone())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The frecency bonus of every remembered file, by [`Hit::key`].
|
||||||
|
pub(crate) fn bonuses(&self) -> HashMap<(bool, String), i64> {
|
||||||
|
let now = now();
|
||||||
|
self.visits
|
||||||
|
.borrow()
|
||||||
|
.iter()
|
||||||
|
.map(|visit| {
|
||||||
|
(
|
||||||
|
visit.hit.key(),
|
||||||
|
frecency_bonus(visit.count, now - visit.last),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn now() -> i64 {
|
||||||
|
std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.map_or(0, |d| d.as_secs() as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use pdfs_core::control::{LocalHit, SearchHit};
|
||||||
|
|
||||||
|
const DAY: i64 = 86_400;
|
||||||
|
|
||||||
|
fn drive(name: &str) -> Hit {
|
||||||
|
Hit::Drive(SearchHit {
|
||||||
|
name: name.into(),
|
||||||
|
path: format!("Docs/{name}"),
|
||||||
|
is_dir: false,
|
||||||
|
size: 1,
|
||||||
|
modified: 1,
|
||||||
|
pinned: false,
|
||||||
|
cached: false,
|
||||||
|
uid: format!("v~{name}"),
|
||||||
|
mounted_path: None,
|
||||||
|
score: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scratch_dir(test: &str) -> PathBuf {
|
||||||
|
let dir = std::env::temp_dir().join(format!(
|
||||||
|
"pdfs-prompt-history-{test}-{}-{}",
|
||||||
|
std::process::id(),
|
||||||
|
now()
|
||||||
|
));
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_bonus_halves_every_two_weeks() {
|
||||||
|
assert_eq!(frecency_bonus(1, 0), 1500);
|
||||||
|
assert_eq!(frecency_bonus(1, 14 * DAY), 750);
|
||||||
|
assert_eq!(frecency_bonus(1, 28 * DAY), 375);
|
||||||
|
assert_eq!(frecency_bonus(2, 14 * DAY), 1500);
|
||||||
|
// A clock that went backwards counts as "just now", not as a boost.
|
||||||
|
assert_eq!(frecency_bonus(1, -DAY), 1500);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_bonus_is_capped() {
|
||||||
|
assert_eq!(frecency_bonus(4, 0), 6000);
|
||||||
|
assert_eq!(frecency_bonus(1000, 0), 6000);
|
||||||
|
assert!(frecency_bonus(1000, 365 * DAY) < 6000);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_repeat_visit_moves_the_file_to_the_top_and_counts_it() {
|
||||||
|
let mut visits = Vec::new();
|
||||||
|
record(&mut visits, drive("a.pdf"), 10);
|
||||||
|
record(&mut visits, drive("b.pdf"), 20);
|
||||||
|
record(&mut visits, drive("a.pdf"), 30);
|
||||||
|
assert_eq!(visits.len(), 2);
|
||||||
|
assert_eq!(visits[0].hit.name(), "a.pdf");
|
||||||
|
assert_eq!((visits[0].count, visits[0].last), (2, 30));
|
||||||
|
assert_eq!(visits[1].count, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_history_keeps_the_newest_fifty_files() {
|
||||||
|
let mut visits = Vec::new();
|
||||||
|
for n in 0..60 {
|
||||||
|
record(&mut visits, drive(&format!("{n}.pdf")), n);
|
||||||
|
}
|
||||||
|
assert_eq!(visits.len(), MAX_VISITS);
|
||||||
|
assert_eq!(visits[0].hit.name(), "59.pdf");
|
||||||
|
assert_eq!(visits[MAX_VISITS - 1].hit.name(), "10.pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_history_file_round_trips() {
|
||||||
|
let dir = scratch_dir("round-trip");
|
||||||
|
let path = dir.join("prompt-history.json");
|
||||||
|
let mut visits = Vec::new();
|
||||||
|
record(&mut visits, drive("a.pdf"), 10);
|
||||||
|
record(&mut visits, drive("a.pdf"), 20);
|
||||||
|
save(&path, &visits).unwrap();
|
||||||
|
|
||||||
|
let loaded = load(&path);
|
||||||
|
assert_eq!(loaded.len(), 1);
|
||||||
|
assert_eq!(loaded[0].hit.key(), drive("a.pdf").key());
|
||||||
|
assert_eq!((loaded[0].count, loaded[0].last), (2, 20));
|
||||||
|
assert!(!path.with_extension("json.tmp").exists());
|
||||||
|
std::fs::remove_dir_all(dir).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn visits_recorded_before_the_load_are_merged_into_it() {
|
||||||
|
let dir = scratch_dir("merge");
|
||||||
|
let path = dir.join("prompt-history.json");
|
||||||
|
let mut stored = Vec::new();
|
||||||
|
record(&mut stored, drive("old.pdf"), 10);
|
||||||
|
record(&mut stored, drive("old.pdf"), 20);
|
||||||
|
save(&path, &stored).unwrap();
|
||||||
|
|
||||||
|
// Both visits are queued before the writer has read the file.
|
||||||
|
let (commands, inbox) = async_channel::unbounded();
|
||||||
|
commands
|
||||||
|
.send_blocking(Command::Record(drive("old.pdf"), 30))
|
||||||
|
.unwrap();
|
||||||
|
commands
|
||||||
|
.send_blocking(Command::Record(drive("new.pdf"), 40))
|
||||||
|
.unwrap();
|
||||||
|
drop(commands);
|
||||||
|
|
||||||
|
let published = RefCell::new(Vec::new());
|
||||||
|
serve(&path, &inbox, |visits| published.borrow_mut().push(visits));
|
||||||
|
|
||||||
|
let last = published.borrow().last().cloned().unwrap();
|
||||||
|
assert_eq!(last.len(), 2);
|
||||||
|
assert_eq!(last[0].hit.name(), "new.pdf");
|
||||||
|
assert_eq!((last[1].hit.name(), last[1].count), ("old.pdf", 3));
|
||||||
|
let on_disk = load(&path);
|
||||||
|
assert_eq!(on_disk.len(), 2);
|
||||||
|
assert_eq!(on_disk[1].count, 3);
|
||||||
|
std::fs::remove_dir_all(dir).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pruning_drops_deleted_local_files_only() {
|
||||||
|
let dir = scratch_dir("prune");
|
||||||
|
let kept = dir.join("kept.txt");
|
||||||
|
std::fs::write(&kept, "x").unwrap();
|
||||||
|
let local = |path: &Path| {
|
||||||
|
Hit::Local(LocalHit {
|
||||||
|
name: path.file_name().unwrap().to_string_lossy().into_owned(),
|
||||||
|
path: path.display().to_string(),
|
||||||
|
is_dir: false,
|
||||||
|
size: 1,
|
||||||
|
modified: 1,
|
||||||
|
score: 0,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let mut visits = Vec::new();
|
||||||
|
record(&mut visits, local(&kept), 1);
|
||||||
|
record(&mut visits, local(&dir.join("gone.txt")), 2);
|
||||||
|
record(&mut visits, drive("remote.pdf"), 3);
|
||||||
|
|
||||||
|
assert!(prune(&mut visits));
|
||||||
|
let names: Vec<&str> = visits.iter().map(|visit| visit.hit.name()).collect();
|
||||||
|
assert_eq!(names, ["remote.pdf", "kept.txt"]);
|
||||||
|
std::fs::remove_dir_all(dir).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
//! The preview pane beside the results: a thumbnail of the selected hit, what
|
||||||
|
//! it is, where it is and whether it is on this computer, and the actions for
|
||||||
|
//! it with their shortcuts.
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use adw::prelude::*;
|
||||||
|
use gtk4::{gio, glib};
|
||||||
|
|
||||||
|
use super::row::{fill_slot, thumb_slot};
|
||||||
|
use super::thumbs::Thumbs;
|
||||||
|
use crate::{Hit, dates, gettext, human_bytes, pgettext};
|
||||||
|
|
||||||
|
/// Width of the pane.
|
||||||
|
pub(crate) const PREVIEW_WIDTH: i32 = 300;
|
||||||
|
|
||||||
|
/// The thumbnail area, inside the pane's padding.
|
||||||
|
const THUMB_WIDTH: i32 = 268;
|
||||||
|
const THUMB_HEIGHT: i32 = 180;
|
||||||
|
|
||||||
|
/// How long "Path copied" stays up.
|
||||||
|
const COPIED_FEEDBACK: Duration = Duration::from_millis(1500);
|
||||||
|
|
||||||
|
/// One label/value line of the details grid.
|
||||||
|
struct Detail {
|
||||||
|
title: gtk4::Label,
|
||||||
|
value: gtk4::Label,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Detail {
|
||||||
|
fn new(grid: >k4::Grid, row: i32, title: &str) -> Self {
|
||||||
|
let title = gtk4::Label::builder()
|
||||||
|
.label(title)
|
||||||
|
.xalign(0.0)
|
||||||
|
.valign(gtk4::Align::Start)
|
||||||
|
.build();
|
||||||
|
title.add_css_class("preview-key");
|
||||||
|
// A wrapping label asks for its whole text on one line as its natural
|
||||||
|
// width, which would widen the pane and squeeze the results. Capping it
|
||||||
|
// keeps the pane at its fixed width; long values wrap, then ellipsize.
|
||||||
|
let value = gtk4::Label::builder()
|
||||||
|
.xalign(0.0)
|
||||||
|
.hexpand(true)
|
||||||
|
.wrap(true)
|
||||||
|
.wrap_mode(gtk4::pango::WrapMode::WordChar)
|
||||||
|
.max_width_chars(1)
|
||||||
|
.lines(3)
|
||||||
|
.ellipsize(gtk4::pango::EllipsizeMode::Middle)
|
||||||
|
.build();
|
||||||
|
value.add_css_class("preview-value");
|
||||||
|
grid.attach(&title, 0, row, 1, 1);
|
||||||
|
grid.attach(&value, 1, row, 1, 1);
|
||||||
|
Self { title, value }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show `value`, or hide the line when there is nothing to say.
|
||||||
|
fn set(&self, value: Option<&str>) {
|
||||||
|
self.title.set_visible(value.is_some());
|
||||||
|
self.value.set_visible(value.is_some());
|
||||||
|
self.value.set_label(value.unwrap_or_default());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Preview {
|
||||||
|
pub(crate) root: gtk4::Box,
|
||||||
|
stack: gtk4::Stack,
|
||||||
|
slot: gtk4::Overlay,
|
||||||
|
name: gtk4::Label,
|
||||||
|
kind: gtk4::Label,
|
||||||
|
size: Detail,
|
||||||
|
modified: Detail,
|
||||||
|
location: Detail,
|
||||||
|
availability: Detail,
|
||||||
|
availability_icon: gtk4::Image,
|
||||||
|
pub(crate) open: gtk4::Button,
|
||||||
|
open_label: gtk4::Label,
|
||||||
|
pub(crate) reveal: gtk4::Button,
|
||||||
|
pub(crate) copy: gtk4::Button,
|
||||||
|
copied: gtk4::Revealer,
|
||||||
|
copied_timer: Rc<RefCell<Option<glib::SourceId>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A flat action button: the label, and its shortcut at the far end.
|
||||||
|
fn action(icon: &str, label: &str, accelerator: &str) -> (gtk4::Button, gtk4::Label) {
|
||||||
|
let content = gtk4::Box::new(gtk4::Orientation::Horizontal, 10);
|
||||||
|
content.append(>k4::Image::from_icon_name(icon));
|
||||||
|
let text = gtk4::Label::builder()
|
||||||
|
.label(label)
|
||||||
|
.xalign(0.0)
|
||||||
|
.hexpand(true)
|
||||||
|
.max_width_chars(1)
|
||||||
|
.ellipsize(gtk4::pango::EllipsizeMode::End)
|
||||||
|
.build();
|
||||||
|
content.append(&text);
|
||||||
|
let shortcut = gtk4::ShortcutLabel::new(accelerator);
|
||||||
|
shortcut.add_css_class("preview-shortcut");
|
||||||
|
content.append(&shortcut);
|
||||||
|
let button = gtk4::Button::builder()
|
||||||
|
.child(&content)
|
||||||
|
.focus_on_click(false)
|
||||||
|
.focusable(false)
|
||||||
|
.build();
|
||||||
|
button.add_css_class("flat");
|
||||||
|
button.add_css_class("preview-action");
|
||||||
|
(button, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Preview {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
let details = gtk4::Box::new(gtk4::Orientation::Vertical, 0);
|
||||||
|
|
||||||
|
let slot = thumb_slot(THUMB_WIDTH, THUMB_HEIGHT, 96, gtk4::ContentFit::Contain);
|
||||||
|
slot.add_css_class("preview-thumb");
|
||||||
|
details.append(&slot);
|
||||||
|
|
||||||
|
let name = gtk4::Label::builder()
|
||||||
|
.xalign(0.0)
|
||||||
|
.wrap(true)
|
||||||
|
.wrap_mode(gtk4::pango::WrapMode::WordChar)
|
||||||
|
.max_width_chars(1)
|
||||||
|
.lines(3)
|
||||||
|
.ellipsize(gtk4::pango::EllipsizeMode::End)
|
||||||
|
.build();
|
||||||
|
name.add_css_class("preview-name");
|
||||||
|
details.append(&name);
|
||||||
|
|
||||||
|
let kind = gtk4::Label::builder()
|
||||||
|
.xalign(0.0)
|
||||||
|
.max_width_chars(1)
|
||||||
|
.ellipsize(gtk4::pango::EllipsizeMode::End)
|
||||||
|
.build();
|
||||||
|
kind.add_css_class("preview-kind");
|
||||||
|
details.append(&kind);
|
||||||
|
|
||||||
|
let grid = gtk4::Grid::builder()
|
||||||
|
.row_spacing(6)
|
||||||
|
.column_spacing(12)
|
||||||
|
.build();
|
||||||
|
grid.add_css_class("preview-details");
|
||||||
|
let size = Detail::new(&grid, 0, &pgettext("column", "Size"));
|
||||||
|
let modified = Detail::new(&grid, 1, &pgettext("column", "Modified"));
|
||||||
|
let location = Detail::new(&grid, 2, &gettext("Location"));
|
||||||
|
// Translators: a heading in the launcher's preview pane, followed by
|
||||||
|
// "Available offline", "Downloaded", "Online only" or "On this computer".
|
||||||
|
let availability = Detail::new(&grid, 3, &gettext("Availability"));
|
||||||
|
// The availability value is an icon and a text.
|
||||||
|
let availability_icon = gtk4::Image::new();
|
||||||
|
availability_icon.add_css_class("preview-badge");
|
||||||
|
grid.remove(&availability.value);
|
||||||
|
let availability_box = gtk4::Box::new(gtk4::Orientation::Horizontal, 6);
|
||||||
|
availability_box.append(&availability_icon);
|
||||||
|
availability_box.append(&availability.value);
|
||||||
|
grid.attach(&availability_box, 1, 3, 1, 1);
|
||||||
|
details.append(&grid);
|
||||||
|
|
||||||
|
let spacer = gtk4::Box::builder().vexpand(true).build();
|
||||||
|
details.append(&spacer);
|
||||||
|
|
||||||
|
let actions = gtk4::Box::new(gtk4::Orientation::Vertical, 2);
|
||||||
|
actions.add_css_class("preview-actions");
|
||||||
|
let (open, open_label) = action("document-open-symbolic", "", "Return");
|
||||||
|
let (reveal, _) = action(
|
||||||
|
"folder-open-symbolic",
|
||||||
|
&gettext("Show in Folder"),
|
||||||
|
"<Control>Return",
|
||||||
|
);
|
||||||
|
let (copy, _) = action("edit-copy-symbolic", &gettext("Copy Path"), "<Control>c");
|
||||||
|
actions.append(&open);
|
||||||
|
actions.append(&reveal);
|
||||||
|
actions.append(©);
|
||||||
|
details.append(&actions);
|
||||||
|
|
||||||
|
let copied_label = gtk4::Label::new(Some(&gettext("Path copied")));
|
||||||
|
copied_label.add_css_class("preview-feedback");
|
||||||
|
let copied = gtk4::Revealer::builder()
|
||||||
|
.transition_type(gtk4::RevealerTransitionType::Crossfade)
|
||||||
|
.child(&copied_label)
|
||||||
|
.build();
|
||||||
|
details.append(&copied);
|
||||||
|
|
||||||
|
let empty = gtk4::Image::from_icon_name("system-search-symbolic");
|
||||||
|
empty.set_pixel_size(64);
|
||||||
|
empty.add_css_class("preview-empty");
|
||||||
|
|
||||||
|
let stack = gtk4::Stack::builder()
|
||||||
|
.transition_type(gtk4::StackTransitionType::Crossfade)
|
||||||
|
.transition_duration(100)
|
||||||
|
.vexpand(true)
|
||||||
|
.build();
|
||||||
|
stack.add_named(&empty, Some("empty"));
|
||||||
|
stack.add_named(&details, Some("details"));
|
||||||
|
|
||||||
|
let root = gtk4::Box::builder()
|
||||||
|
.orientation(gtk4::Orientation::Vertical)
|
||||||
|
.width_request(PREVIEW_WIDTH)
|
||||||
|
.hexpand(false)
|
||||||
|
.build();
|
||||||
|
root.add_css_class("preview");
|
||||||
|
root.append(&stack);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
root,
|
||||||
|
stack,
|
||||||
|
slot,
|
||||||
|
name,
|
||||||
|
kind,
|
||||||
|
size,
|
||||||
|
modified,
|
||||||
|
location,
|
||||||
|
availability,
|
||||||
|
availability_icon,
|
||||||
|
open,
|
||||||
|
open_label,
|
||||||
|
reveal,
|
||||||
|
copy,
|
||||||
|
copied,
|
||||||
|
copied_timer: Rc::new(RefCell::new(None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show `hit`, or the empty pane when nothing is selected.
|
||||||
|
pub(crate) fn show(&self, hit: Option<&Hit>, thumbs: &Rc<Thumbs>, mountpoint: &Path) {
|
||||||
|
let Some(hit) = hit else {
|
||||||
|
self.stack.set_visible_child_name("empty");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.stack.set_visible_child_name("details");
|
||||||
|
fill_slot(&self.slot, hit, thumbs, mountpoint);
|
||||||
|
|
||||||
|
self.name.set_label(hit.name());
|
||||||
|
self.name.set_tooltip_text(Some(hit.name()));
|
||||||
|
let content_type = if hit.is_dir() {
|
||||||
|
glib::GString::from("inode/directory")
|
||||||
|
} else {
|
||||||
|
gio::functions::content_type_guess(Some(hit.name()), None).0
|
||||||
|
};
|
||||||
|
self.kind
|
||||||
|
.set_label(&gio::functions::content_type_get_description(&content_type));
|
||||||
|
|
||||||
|
let size = (!hit.is_dir() && hit.size() > 0).then(|| human_bytes(hit.size()));
|
||||||
|
self.size.set(size.as_deref());
|
||||||
|
|
||||||
|
let modified = (hit.modified() > 0).then(|| dates::relative(hit.modified()));
|
||||||
|
self.modified.set(modified.as_deref());
|
||||||
|
self.modified.value.set_tooltip_text(
|
||||||
|
(hit.modified() > 0)
|
||||||
|
.then(|| dates::full(hit.modified()))
|
||||||
|
.as_deref(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let location = hit.location();
|
||||||
|
self.location.set(Some(&location));
|
||||||
|
self.location.value.set_tooltip_text(Some(&location));
|
||||||
|
|
||||||
|
match availability(hit) {
|
||||||
|
Some((icon, class, text)) => {
|
||||||
|
self.availability.set(Some(&text));
|
||||||
|
self.availability_icon.set_icon_name(Some(icon));
|
||||||
|
for other in ["badge-pinned", "badge-cached", "badge-cloud", "badge-local"] {
|
||||||
|
self.availability_icon.remove_css_class(other);
|
||||||
|
}
|
||||||
|
self.availability_icon.add_css_class(class);
|
||||||
|
self.availability_icon.set_visible(true);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.availability.set(None);
|
||||||
|
self.availability_icon.set_visible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.open_label.set_label(&if hit.is_dir() {
|
||||||
|
gettext("Open folder")
|
||||||
|
} else {
|
||||||
|
pgettext("verb", "Open")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Say "Path copied" for a moment.
|
||||||
|
pub(crate) fn flash_copied(&self) {
|
||||||
|
self.copied.set_reveal_child(true);
|
||||||
|
if let Some(source) = self.copied_timer.borrow_mut().take() {
|
||||||
|
source.remove();
|
||||||
|
}
|
||||||
|
let copied = self.copied.clone();
|
||||||
|
let slot = self.copied_timer.clone();
|
||||||
|
let timer = glib::timeout_add_local_once(COPIED_FEEDBACK, move || {
|
||||||
|
slot.borrow_mut().take();
|
||||||
|
copied.set_reveal_child(false);
|
||||||
|
});
|
||||||
|
*self.copied_timer.borrow_mut() = Some(timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where the hit's content is: its icon, its style class and its wording.
|
||||||
|
fn availability(hit: &Hit) -> Option<(&'static str, &'static str, String)> {
|
||||||
|
match hit {
|
||||||
|
Hit::Local(_) => Some((
|
||||||
|
"drive-harddisk-symbolic",
|
||||||
|
"badge-local",
|
||||||
|
gettext("On this computer"),
|
||||||
|
)),
|
||||||
|
Hit::Drive(drive) if drive.pinned => Some((
|
||||||
|
"pdfs-offline-symbolic",
|
||||||
|
"badge-pinned",
|
||||||
|
gettext("Available offline"),
|
||||||
|
)),
|
||||||
|
// A folder is never downloaded as a whole; only pinning says anything.
|
||||||
|
Hit::Drive(drive) if drive.is_dir => None,
|
||||||
|
Hit::Drive(drive) if drive.cached => Some((
|
||||||
|
"pdfs-cached-symbolic",
|
||||||
|
"badge-cached",
|
||||||
|
pgettext("file state", "Downloaded"),
|
||||||
|
)),
|
||||||
|
Hit::Drive(_) => Some((
|
||||||
|
"pdfs-online-only-symbolic",
|
||||||
|
"badge-cloud",
|
||||||
|
pgettext("file state", "Online only"),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
//! Result rows: a thumbnail or icon, the name with its matched characters in
|
||||||
|
//! bold, the location, and on the right the sync state, size and age.
|
||||||
|
|
||||||
|
use std::cell::Cell;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use adw::prelude::*;
|
||||||
|
use gtk4::{gdk, gio};
|
||||||
|
|
||||||
|
use super::highlight;
|
||||||
|
use super::thumbs::Thumbs;
|
||||||
|
use crate::theme::proton_theme_active;
|
||||||
|
use crate::{Hit, format_age, human_bytes};
|
||||||
|
|
||||||
|
/// Edge of a row's thumbnail.
|
||||||
|
const ROW_THUMB: i32 = 40;
|
||||||
|
|
||||||
|
/// A thumbnail surface: an icon, with a picture laid over it that shows once
|
||||||
|
/// the thumbnail has loaded. [`Thumbs`] paints the picture.
|
||||||
|
pub(crate) fn thumb_slot(
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
icon_size: i32,
|
||||||
|
fit: gtk4::ContentFit,
|
||||||
|
) -> gtk4::Overlay {
|
||||||
|
let fallback = gtk4::Image::builder().pixel_size(icon_size).build();
|
||||||
|
let picture = gtk4::Picture::builder()
|
||||||
|
.width_request(width)
|
||||||
|
.height_request(height)
|
||||||
|
.content_fit(fit)
|
||||||
|
.can_shrink(true)
|
||||||
|
.visible(false)
|
||||||
|
.build();
|
||||||
|
picture.set_overflow(gtk4::Overflow::Hidden);
|
||||||
|
picture.add_css_class("thumb");
|
||||||
|
|
||||||
|
let overlay = gtk4::Overlay::new();
|
||||||
|
overlay.set_size_request(width, height);
|
||||||
|
overlay.set_halign(gtk4::Align::Center);
|
||||||
|
overlay.set_valign(gtk4::Align::Center);
|
||||||
|
overlay.set_child(Some(&fallback));
|
||||||
|
overlay.add_overlay(&picture);
|
||||||
|
overlay
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show `hit` in a slot from [`thumb_slot`]: its icon at once, and its
|
||||||
|
/// thumbnail when there is one.
|
||||||
|
pub(crate) fn fill_slot(slot: >k4::Overlay, hit: &Hit, thumbs: &Rc<Thumbs>, mountpoint: &Path) {
|
||||||
|
let Some(fallback) = slot.child().and_downcast::<gtk4::Image>() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(picture) = fallback.next_sibling().and_downcast::<gtk4::Picture>() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
fallback.set_from_gicon(&icon_for(hit.name(), hit.is_dir()));
|
||||||
|
fallback.set_visible(true);
|
||||||
|
picture.set_visible(false);
|
||||||
|
picture.set_paintable(gdk::Paintable::NONE);
|
||||||
|
thumbs.want(hit, mountpoint, &picture);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The desktop's own full-colour icon for a file name, so results look like
|
||||||
|
/// the user's file manager. Folders are Proton's purple ones while the Proton
|
||||||
|
/// theme is on, as in the app's browser.
|
||||||
|
pub(crate) fn icon_for(name: &str, is_dir: bool) -> gio::Icon {
|
||||||
|
if is_dir {
|
||||||
|
let folder = if proton_theme_active() {
|
||||||
|
"pdfs-proton-folder"
|
||||||
|
} else {
|
||||||
|
"folder"
|
||||||
|
};
|
||||||
|
return gio::ThemedIcon::new(folder).upcast();
|
||||||
|
}
|
||||||
|
let (content_type, _uncertain) = gio::functions::content_type_guess(Some(name), None);
|
||||||
|
gio::functions::content_type_get_icon(&content_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The sync badge of a Drive file, as in the app's browser: pinned, cached,
|
||||||
|
/// or online only. Folders and local files have none.
|
||||||
|
pub(crate) fn badge_for(hit: &Hit) -> Option<(&'static str, &'static str)> {
|
||||||
|
let Hit::Drive(drive) = hit else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
if drive.is_dir {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(if drive.pinned {
|
||||||
|
("pdfs-offline-symbolic", "badge-pinned")
|
||||||
|
} else if drive.cached {
|
||||||
|
("pdfs-cached-symbolic", "badge-cached")
|
||||||
|
} else {
|
||||||
|
("pdfs-online-only-symbolic", "badge-cloud")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn build_row(
|
||||||
|
hit: &Hit,
|
||||||
|
query: &str,
|
||||||
|
thumbs: &Rc<Thumbs>,
|
||||||
|
mountpoint: &Path,
|
||||||
|
) -> gtk4::ListBoxRow {
|
||||||
|
let row = gtk4::ListBoxRow::new();
|
||||||
|
row.add_css_class("result-row");
|
||||||
|
// Focus stays in the search entry; the list only shows the cursor.
|
||||||
|
row.set_focusable(false);
|
||||||
|
|
||||||
|
let content = gtk4::Box::new(gtk4::Orientation::Horizontal, 12);
|
||||||
|
|
||||||
|
let slot = thumb_slot(ROW_THUMB, ROW_THUMB, 32, gtk4::ContentFit::Cover);
|
||||||
|
fill_slot(&slot, hit, thumbs, mountpoint);
|
||||||
|
content.append(&slot);
|
||||||
|
|
||||||
|
let text = gtk4::Box::new(gtk4::Orientation::Vertical, 1);
|
||||||
|
text.set_hexpand(true);
|
||||||
|
text.set_valign(gtk4::Align::Center);
|
||||||
|
|
||||||
|
let name = gtk4::Label::builder()
|
||||||
|
.label(highlight::markup(hit.name(), query))
|
||||||
|
.use_markup(true)
|
||||||
|
.xalign(0.0)
|
||||||
|
.ellipsize(gtk4::pango::EllipsizeMode::Middle)
|
||||||
|
.build();
|
||||||
|
name.add_css_class("result-name");
|
||||||
|
text.append(&name);
|
||||||
|
|
||||||
|
let location = gtk4::Label::builder()
|
||||||
|
.label(hit.location())
|
||||||
|
.xalign(0.0)
|
||||||
|
.ellipsize(gtk4::pango::EllipsizeMode::Start)
|
||||||
|
.build();
|
||||||
|
location.add_css_class("result-location");
|
||||||
|
text.append(&location);
|
||||||
|
content.append(&text);
|
||||||
|
|
||||||
|
let meta = gtk4::Box::new(gtk4::Orientation::Horizontal, 10);
|
||||||
|
meta.set_valign(gtk4::Align::Center);
|
||||||
|
if let Some((icon, class)) = badge_for(hit) {
|
||||||
|
let badge = gtk4::Image::from_icon_name(icon);
|
||||||
|
badge.add_css_class("result-badge");
|
||||||
|
badge.add_css_class(class);
|
||||||
|
meta.append(&badge);
|
||||||
|
}
|
||||||
|
if !hit.is_dir() && hit.size() > 0 {
|
||||||
|
let size = gtk4::Label::new(Some(&human_bytes(hit.size())));
|
||||||
|
size.add_css_class("result-meta");
|
||||||
|
meta.append(&size);
|
||||||
|
}
|
||||||
|
if hit.modified() > 0 {
|
||||||
|
let modified = gtk4::Label::new(Some(&format_age(hit.modified())));
|
||||||
|
modified.add_css_class("result-meta");
|
||||||
|
meta.append(&modified);
|
||||||
|
}
|
||||||
|
content.append(&meta);
|
||||||
|
|
||||||
|
row.set_child(Some(&content));
|
||||||
|
row
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Let a row be dragged into a file manager or an application as a file.
|
||||||
|
/// `dropped` runs once a drop was accepted; a cancelled drag changes nothing.
|
||||||
|
pub(crate) fn attach_drag(row: >k4::ListBoxRow, path: PathBuf, dropped: impl Fn() + 'static) {
|
||||||
|
let source = gtk4::DragSource::new();
|
||||||
|
source.set_actions(gdk::DragAction::COPY);
|
||||||
|
let files = gdk::FileList::from_array(&[gio::File::for_path(&path)]);
|
||||||
|
source.set_content(Some(&gdk::ContentProvider::for_value(&files.to_value())));
|
||||||
|
|
||||||
|
let cancelled = Rc::new(Cell::new(false));
|
||||||
|
let begin = cancelled.clone();
|
||||||
|
source.connect_drag_begin(move |source, _| {
|
||||||
|
begin.set(false);
|
||||||
|
if let Some(widget) = source.widget() {
|
||||||
|
source.set_icon(Some(>k4::WidgetPaintable::new(Some(&widget))), 0, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let cancel = cancelled.clone();
|
||||||
|
source.connect_drag_cancel(move |_, _, _| {
|
||||||
|
cancel.set(true);
|
||||||
|
false
|
||||||
|
});
|
||||||
|
source.connect_drag_end(move |_, _, _| {
|
||||||
|
if !cancelled.get() {
|
||||||
|
dropped();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
row.add_controller(source);
|
||||||
|
}
|
||||||
@@ -0,0 +1,697 @@
|
|||||||
|
//! Thumbnails for the launcher's rows and preview pane.
|
||||||
|
//!
|
||||||
|
//! The main thread keeps a small LRU of textures, the keys known to have no
|
||||||
|
//! thumbnail, and the pictures waiting for one (as weak references, so a row
|
||||||
|
//! that was rebuilt or dropped keeps nothing alive). Everything that touches
|
||||||
|
//! a file runs on two worker threads, which try in order:
|
||||||
|
//!
|
||||||
|
//! 1. the freedesktop thumbnail cache, valid only when its `Thumb::MTime`
|
||||||
|
//! matches the file's;
|
||||||
|
//! 2. for local images, decoding the file itself, and writing the result back
|
||||||
|
//! to that cache for the next time and for every other application;
|
||||||
|
//! 3. for Drive images, the daemon's `FileThumbs`, batched after a short quiet
|
||||||
|
//! period.
|
||||||
|
//!
|
||||||
|
//! Workers hand back `gdk::Texture`s, never a `Pixbuf`, which may not cross
|
||||||
|
//! threads. Each job carries the render epoch it was queued in, and workers
|
||||||
|
//! skip jobs a newer query has made pointless.
|
||||||
|
|
||||||
|
use std::cell::{Cell, RefCell};
|
||||||
|
use std::collections::{HashMap, HashSet, VecDeque};
|
||||||
|
use std::io::Write;
|
||||||
|
use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::rc::{Rc, Weak};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
use std::time::{Duration, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use gtk4::gdk_pixbuf::Pixbuf;
|
||||||
|
use gtk4::prelude::*;
|
||||||
|
use gtk4::{gdk, glib};
|
||||||
|
|
||||||
|
use pdfs_core::control::{FileThumbRequest, Request, Response, is_thumbnail_image_name};
|
||||||
|
|
||||||
|
use crate::{Hit, spawn_request};
|
||||||
|
|
||||||
|
/// Decoded textures kept in memory. A launcher shows a few dozen rows, so
|
||||||
|
/// this covers several searches back.
|
||||||
|
const TEXTURE_CACHE_MAX: usize = 128;
|
||||||
|
const MISSING_MAX: usize = 1024;
|
||||||
|
const WORKERS: usize = 2;
|
||||||
|
|
||||||
|
/// Edge of the thumbnails this loader decodes and writes: the freedesktop
|
||||||
|
/// "large" size.
|
||||||
|
const THUMB_SIZE: i32 = 256;
|
||||||
|
|
||||||
|
/// Local images above this size are not decoded here; a thumbnail is not
|
||||||
|
/// worth reading that much.
|
||||||
|
const MAX_SOURCE_BYTES: u64 = 64 * 1024 * 1024;
|
||||||
|
|
||||||
|
/// Quiet period before Drive misses go to the daemon as one batch, so typing
|
||||||
|
/// a word asks once for the final result set.
|
||||||
|
const DAEMON_DEBOUNCE: Duration = Duration::from_millis(150);
|
||||||
|
const DAEMON_RETRY: Duration = Duration::from_secs(1);
|
||||||
|
const DAEMON_BATCH: usize = 32;
|
||||||
|
|
||||||
|
/// Where a job finds its image.
|
||||||
|
enum Source {
|
||||||
|
/// A file on this machine.
|
||||||
|
Local(PathBuf),
|
||||||
|
/// A Drive file: the thumbnail cache is keyed by its mounted path, and
|
||||||
|
/// the daemon is asked on a miss.
|
||||||
|
Drive {
|
||||||
|
mounted: PathBuf,
|
||||||
|
request: FileThumbRequest,
|
||||||
|
},
|
||||||
|
/// A thumbnail the daemon made, in its own cache.
|
||||||
|
Daemon(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Job {
|
||||||
|
epoch: u64,
|
||||||
|
key: String,
|
||||||
|
source: Source,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Outcome {
|
||||||
|
Texture(gdk::Texture),
|
||||||
|
Missing,
|
||||||
|
/// Not in the thumbnail cache; the daemon has to make it.
|
||||||
|
Daemon(FileThumbRequest),
|
||||||
|
/// Queued for an older render; handed back in case it is wanted again.
|
||||||
|
Skipped(Source),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Done {
|
||||||
|
key: String,
|
||||||
|
outcome: Outcome,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Thumbs {
|
||||||
|
socket: PathBuf,
|
||||||
|
epoch: Arc<AtomicU64>,
|
||||||
|
jobs: async_channel::Sender<Job>,
|
||||||
|
|
||||||
|
textures: RefCell<HashMap<String, gdk::Texture>>,
|
||||||
|
/// Least recently used first.
|
||||||
|
order: RefCell<VecDeque<String>>,
|
||||||
|
missing: RefCell<HashSet<String>>,
|
||||||
|
missing_order: RefCell<VecDeque<String>>,
|
||||||
|
waiters: RefCell<HashMap<String, Vec<glib::WeakRef<gtk4::Picture>>>>,
|
||||||
|
/// Keys with a worker job or a daemon request outstanding.
|
||||||
|
working: RefCell<HashSet<String>>,
|
||||||
|
|
||||||
|
/// Drive misses waiting for the next batch, by key.
|
||||||
|
queued: RefCell<HashMap<String, FileThumbRequest>>,
|
||||||
|
/// Drive items the daemon is working on under `generation`, by key.
|
||||||
|
sent: RefCell<HashMap<String, FileThumbRequest>>,
|
||||||
|
generation: Cell<u64>,
|
||||||
|
reserving: Cell<bool>,
|
||||||
|
inflight: Cell<bool>,
|
||||||
|
flush: RefCell<Option<glib::SourceId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Thumbs {
|
||||||
|
pub(crate) fn new(socket: PathBuf) -> Rc<Self> {
|
||||||
|
let epoch = Arc::new(AtomicU64::new(0));
|
||||||
|
let (jobs, inbox) = async_channel::unbounded::<Job>();
|
||||||
|
let (done, results) = async_channel::unbounded::<Done>();
|
||||||
|
for n in 0..WORKERS {
|
||||||
|
let inbox = inbox.clone();
|
||||||
|
let done = done.clone();
|
||||||
|
let epoch = epoch.clone();
|
||||||
|
let spawned = std::thread::Builder::new()
|
||||||
|
.name(format!("prompt-thumbs-{n}"))
|
||||||
|
.spawn(move || work(n, &inbox, &done, &epoch));
|
||||||
|
if let Err(e) = spawned {
|
||||||
|
tracing::warn!("cannot start a thumbnail worker: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let thumbs = Rc::new(Self {
|
||||||
|
socket,
|
||||||
|
epoch,
|
||||||
|
jobs,
|
||||||
|
textures: RefCell::new(HashMap::new()),
|
||||||
|
order: RefCell::new(VecDeque::new()),
|
||||||
|
missing: RefCell::new(HashSet::new()),
|
||||||
|
missing_order: RefCell::new(VecDeque::new()),
|
||||||
|
waiters: RefCell::new(HashMap::new()),
|
||||||
|
working: RefCell::new(HashSet::new()),
|
||||||
|
queued: RefCell::new(HashMap::new()),
|
||||||
|
sent: RefCell::new(HashMap::new()),
|
||||||
|
generation: Cell::new(0),
|
||||||
|
reserving: Cell::new(false),
|
||||||
|
inflight: Cell::new(false),
|
||||||
|
flush: RefCell::new(None),
|
||||||
|
});
|
||||||
|
let weak = Rc::downgrade(&thumbs);
|
||||||
|
glib::spawn_future_local(async move {
|
||||||
|
while let Ok(done) = results.recv().await {
|
||||||
|
let Some(thumbs) = weak.upgrade() else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
thumbs.finish(done);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thumbs
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Paint `hit`'s thumbnail into `picture` now if it is known, or as soon
|
||||||
|
/// as it is. Folders and non-images keep the slot's icon.
|
||||||
|
pub(crate) fn want(self: &Rc<Self>, hit: &Hit, mountpoint: &Path, picture: >k4::Picture) {
|
||||||
|
let key = hit.thumb_key();
|
||||||
|
// The widget name binds the picture to one key: a late reply for
|
||||||
|
// another hit must not paint into it.
|
||||||
|
picture.set_widget_name(&key);
|
||||||
|
if hit.is_dir() || !is_thumbnail_image_name(hit.name()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if matches!(hit, Hit::Drive(drive) if drive.uid.is_empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let cached = self.textures.borrow().get(&key).cloned();
|
||||||
|
if let Some(texture) = cached {
|
||||||
|
self.touch(&key);
|
||||||
|
paint(picture, &key, &texture);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.missing.borrow().contains(&key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.waiters
|
||||||
|
.borrow_mut()
|
||||||
|
.entry(key.clone())
|
||||||
|
.or_default()
|
||||||
|
.push(picture.downgrade());
|
||||||
|
if !self.working.borrow_mut().insert(key.clone()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let source = match hit {
|
||||||
|
Hit::Local(local) => Source::Local(PathBuf::from(&local.path)),
|
||||||
|
Hit::Drive(drive) => Source::Drive {
|
||||||
|
mounted: hit.fs_path(mountpoint),
|
||||||
|
request: FileThumbRequest {
|
||||||
|
uid: drive.uid.clone(),
|
||||||
|
modified: drive.modified,
|
||||||
|
name: drive.name.clone(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
self.dispatch(key, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The rows are about to be rebuilt: forget their pictures, and let the
|
||||||
|
/// workers skip what was queued for them.
|
||||||
|
pub(crate) fn begin_render(&self) {
|
||||||
|
self.epoch.fetch_add(1, Ordering::SeqCst);
|
||||||
|
self.waiters.borrow_mut().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The rows are rebuilt and have asked for their thumbnails. Drive work
|
||||||
|
/// for hits that are gone is dropped, and cancelled at the daemon when it
|
||||||
|
/// had already been sent.
|
||||||
|
pub(crate) fn end_render(self: &Rc<Self>) {
|
||||||
|
let wanted: HashSet<String> = self.waiters.borrow().keys().cloned().collect();
|
||||||
|
let mut dropped = Vec::new();
|
||||||
|
self.queued.borrow_mut().retain(|key, _| {
|
||||||
|
let keep = wanted.contains(key);
|
||||||
|
if !keep {
|
||||||
|
dropped.push(key.clone());
|
||||||
|
}
|
||||||
|
keep
|
||||||
|
});
|
||||||
|
let stale = self.sent.borrow().keys().any(|key| !wanted.contains(key));
|
||||||
|
if stale {
|
||||||
|
self.cancel_daemon();
|
||||||
|
// Still-wanted items go back into the next batch, under a fresh
|
||||||
|
// generation.
|
||||||
|
let sent: Vec<(String, FileThumbRequest)> = self.sent.borrow_mut().drain().collect();
|
||||||
|
for (key, request) in sent {
|
||||||
|
if wanted.contains(&key) {
|
||||||
|
self.queued.borrow_mut().insert(key, request);
|
||||||
|
} else {
|
||||||
|
dropped.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.schedule(DAEMON_DEBOUNCE);
|
||||||
|
}
|
||||||
|
let mut working = self.working.borrow_mut();
|
||||||
|
for key in dropped {
|
||||||
|
working.remove(&key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The launcher was dismissed: stop everything.
|
||||||
|
pub(crate) fn cancel_all(&self) {
|
||||||
|
self.epoch.fetch_add(1, Ordering::SeqCst);
|
||||||
|
self.waiters.borrow_mut().clear();
|
||||||
|
self.working.borrow_mut().clear();
|
||||||
|
self.queued.borrow_mut().clear();
|
||||||
|
self.sent.borrow_mut().clear();
|
||||||
|
if let Some(source) = self.flush.borrow_mut().take() {
|
||||||
|
source.remove();
|
||||||
|
}
|
||||||
|
self.cancel_daemon();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatch(&self, key: String, source: Source) {
|
||||||
|
let job = Job {
|
||||||
|
epoch: self.epoch.load(Ordering::SeqCst),
|
||||||
|
key: key.clone(),
|
||||||
|
source,
|
||||||
|
};
|
||||||
|
if self.jobs.try_send(job).is_err() {
|
||||||
|
self.working.borrow_mut().remove(&key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wanted(&self, key: &str) -> bool {
|
||||||
|
self.waiters.borrow().contains_key(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(self: &Rc<Self>, done: Done) {
|
||||||
|
let Done { key, outcome } = done;
|
||||||
|
match outcome {
|
||||||
|
Outcome::Texture(texture) => {
|
||||||
|
self.working.borrow_mut().remove(&key);
|
||||||
|
self.store_texture(&key, texture.clone());
|
||||||
|
let waiters = self.waiters.borrow_mut().remove(&key);
|
||||||
|
for picture in waiters.into_iter().flatten() {
|
||||||
|
if let Some(picture) = picture.upgrade() {
|
||||||
|
paint(&picture, &key, &texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Outcome::Missing => {
|
||||||
|
self.working.borrow_mut().remove(&key);
|
||||||
|
self.store_missing(key.clone());
|
||||||
|
self.waiters.borrow_mut().remove(&key);
|
||||||
|
}
|
||||||
|
Outcome::Daemon(request) => {
|
||||||
|
if self.wanted(&key) {
|
||||||
|
self.queued.borrow_mut().insert(key, request);
|
||||||
|
self.schedule(DAEMON_DEBOUNCE);
|
||||||
|
} else {
|
||||||
|
self.working.borrow_mut().remove(&key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Outcome::Skipped(source) => {
|
||||||
|
// A newer render may want the same image again.
|
||||||
|
if self.wanted(&key) {
|
||||||
|
self.dispatch(key, source);
|
||||||
|
} else {
|
||||||
|
self.working.borrow_mut().remove(&key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn touch(&self, key: &str) {
|
||||||
|
let mut order = self.order.borrow_mut();
|
||||||
|
if let Some(at) = order.iter().position(|held| held == key) {
|
||||||
|
order.remove(at);
|
||||||
|
order.push_back(key.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_texture(&self, key: &str, texture: gdk::Texture) {
|
||||||
|
let mut textures = self.textures.borrow_mut();
|
||||||
|
if textures.insert(key.to_string(), texture).is_none() {
|
||||||
|
self.order.borrow_mut().push_back(key.to_string());
|
||||||
|
} else {
|
||||||
|
drop(textures);
|
||||||
|
self.touch(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut order = self.order.borrow_mut();
|
||||||
|
while order.len() > TEXTURE_CACHE_MAX {
|
||||||
|
if let Some(old) = order.pop_front() {
|
||||||
|
textures.remove(&old);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_missing(&self, key: String) {
|
||||||
|
let mut missing = self.missing.borrow_mut();
|
||||||
|
let mut order = self.missing_order.borrow_mut();
|
||||||
|
if missing.insert(key.clone()) {
|
||||||
|
order.push_back(key);
|
||||||
|
}
|
||||||
|
while order.len() > MISSING_MAX {
|
||||||
|
if let Some(old) = order.pop_front() {
|
||||||
|
missing.remove(&old);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop the daemon's work for this launcher. The generation is shared
|
||||||
|
/// with the app, so this is only sent when something was asked for.
|
||||||
|
fn cancel_daemon(&self) {
|
||||||
|
let generation = self.generation.replace(0);
|
||||||
|
if generation != 0 {
|
||||||
|
// The reply is not needed; `spawn_request` owns the round-trip.
|
||||||
|
drop(spawn_request(
|
||||||
|
self.socket.clone(),
|
||||||
|
Request::CancelFileThumbs {
|
||||||
|
generation: generation.saturating_add(1),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schedule(self: &Rc<Self>, delay: Duration) {
|
||||||
|
if let Some(source) = self.flush.borrow_mut().take() {
|
||||||
|
source.remove();
|
||||||
|
}
|
||||||
|
let weak = Rc::downgrade(self);
|
||||||
|
let source = glib::timeout_add_local_once(delay, move || {
|
||||||
|
if let Some(thumbs) = weak.upgrade() {
|
||||||
|
thumbs.flush.borrow_mut().take();
|
||||||
|
thumbs.send_batch();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*self.flush.borrow_mut() = Some(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_batch(self: &Rc<Self>) {
|
||||||
|
if self.inflight.get() || self.queued.borrow().is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.generation.get() == 0 {
|
||||||
|
self.reserve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let batch: Vec<(String, FileThumbRequest)> = {
|
||||||
|
let mut queued = self.queued.borrow_mut();
|
||||||
|
let keys: Vec<String> = queued.keys().take(DAEMON_BATCH).cloned().collect();
|
||||||
|
keys.into_iter()
|
||||||
|
.filter_map(|key| queued.remove_entry(&key))
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
self.sent.borrow_mut().extend(batch.iter().cloned());
|
||||||
|
let generation = self.generation.get();
|
||||||
|
self.inflight.set(true);
|
||||||
|
let rx = spawn_request(
|
||||||
|
self.socket.clone(),
|
||||||
|
Request::FileThumbs {
|
||||||
|
items: batch.iter().map(|(_, request)| request.clone()).collect(),
|
||||||
|
generation,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let weak = Rc::downgrade(self);
|
||||||
|
glib::spawn_future_local(async move {
|
||||||
|
let reply = rx.recv().await;
|
||||||
|
let Some(thumbs) = weak.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
thumbs.inflight.set(false);
|
||||||
|
thumbs.receive(generation, batch, reply);
|
||||||
|
if !thumbs.queued.borrow().is_empty() {
|
||||||
|
thumbs.schedule(Duration::ZERO);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive(
|
||||||
|
self: &Rc<Self>,
|
||||||
|
generation: u64,
|
||||||
|
batch: Vec<(String, FileThumbRequest)>,
|
||||||
|
reply: Result<Result<Response, String>, async_channel::RecvError>,
|
||||||
|
) {
|
||||||
|
// Cancelled while in flight: `end_render` or `cancel_all` already
|
||||||
|
// re-queued what is still wanted. A finished thumbnail is still worth
|
||||||
|
// painting.
|
||||||
|
let current = generation == self.generation.get();
|
||||||
|
match reply {
|
||||||
|
Ok(Ok(Response::Thumbs { items })) => {
|
||||||
|
let mut by_uid: HashMap<String, (String, FileThumbRequest)> = batch
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, request)| (request.uid.clone(), (key, request)))
|
||||||
|
.collect();
|
||||||
|
let mut again = Vec::new();
|
||||||
|
for item in items {
|
||||||
|
let Some((key, request)) = by_uid.remove(&item.uid) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let Some(path) = item.path {
|
||||||
|
if current {
|
||||||
|
self.sent.borrow_mut().remove(&key);
|
||||||
|
}
|
||||||
|
if self.wanted(&key) {
|
||||||
|
self.working.borrow_mut().insert(key.clone());
|
||||||
|
self.dispatch(key, Source::Daemon(PathBuf::from(path)));
|
||||||
|
} else {
|
||||||
|
self.working.borrow_mut().remove(&key);
|
||||||
|
}
|
||||||
|
} else if !current {
|
||||||
|
continue;
|
||||||
|
} else if item.pending {
|
||||||
|
again.push((key, request));
|
||||||
|
} else {
|
||||||
|
self.sent.borrow_mut().remove(&key);
|
||||||
|
self.working.borrow_mut().remove(&key);
|
||||||
|
self.store_missing(key.clone());
|
||||||
|
self.waiters.borrow_mut().remove(&key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if current {
|
||||||
|
again.extend(by_uid.into_values());
|
||||||
|
self.retry(again);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Ok(Response::FileThumbsStale)) if current => {
|
||||||
|
self.generation.set(0);
|
||||||
|
self.retry(batch);
|
||||||
|
}
|
||||||
|
Ok(Ok(Response::Error { message, .. })) if current => {
|
||||||
|
tracing::debug!("launcher thumbnails failed: {message}");
|
||||||
|
self.retry(batch);
|
||||||
|
}
|
||||||
|
_ if current => self.retry(batch),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ask again for items the daemon is still making, after a pause.
|
||||||
|
fn retry(self: &Rc<Self>, items: Vec<(String, FileThumbRequest)>) {
|
||||||
|
if items.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let weak = Rc::downgrade(self);
|
||||||
|
glib::timeout_add_local_once(DAEMON_RETRY, move || {
|
||||||
|
let Some(thumbs) = weak.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
for (key, request) in items {
|
||||||
|
// Gone from `sent` means cancelled or already answered.
|
||||||
|
if thumbs.sent.borrow_mut().remove(&key).is_some() && thumbs.wanted(&key) {
|
||||||
|
thumbs.queued.borrow_mut().insert(key, request);
|
||||||
|
} else {
|
||||||
|
thumbs.working.borrow_mut().remove(&key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thumbs.send_batch();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reserve(self: &Rc<Self>) {
|
||||||
|
if self.reserving.replace(true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let rx = spawn_request(self.socket.clone(), Request::ReserveFileThumbGeneration);
|
||||||
|
let weak: Weak<Self> = Rc::downgrade(self);
|
||||||
|
glib::spawn_future_local(async move {
|
||||||
|
let reply = rx.recv().await;
|
||||||
|
let Some(thumbs) = weak.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
thumbs.reserving.set(false);
|
||||||
|
match reply {
|
||||||
|
Ok(Ok(Response::FileThumbGeneration { generation })) if generation != 0 => {
|
||||||
|
thumbs.generation.set(generation);
|
||||||
|
thumbs.send_batch();
|
||||||
|
}
|
||||||
|
_ => thumbs.schedule(DAEMON_RETRY),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show `texture` in `picture` if the picture still belongs to `key`, and hide
|
||||||
|
/// the icon underneath it.
|
||||||
|
fn paint(picture: >k4::Picture, key: &str, texture: &gdk::Texture) {
|
||||||
|
if picture.widget_name() != key {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
picture.set_paintable(Some(texture));
|
||||||
|
picture.set_visible(true);
|
||||||
|
if let Some(overlay) = picture.parent().and_downcast::<gtk4::Overlay>()
|
||||||
|
&& let Some(fallback) = overlay.child()
|
||||||
|
{
|
||||||
|
fallback.set_visible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn work(
|
||||||
|
index: usize,
|
||||||
|
inbox: &async_channel::Receiver<Job>,
|
||||||
|
done: &async_channel::Sender<Done>,
|
||||||
|
epoch: &AtomicU64,
|
||||||
|
) {
|
||||||
|
while let Ok(job) = inbox.recv_blocking() {
|
||||||
|
// A daemon thumbnail is already made; decoding it is cheap and the
|
||||||
|
// texture is cached for the next render.
|
||||||
|
let stale =
|
||||||
|
!matches!(job.source, Source::Daemon(_)) && job.epoch < epoch.load(Ordering::SeqCst);
|
||||||
|
let outcome = if stale {
|
||||||
|
Outcome::Skipped(job.source)
|
||||||
|
} else {
|
||||||
|
load(index, job.source)
|
||||||
|
};
|
||||||
|
if done
|
||||||
|
.send_blocking(Done {
|
||||||
|
key: job.key,
|
||||||
|
outcome,
|
||||||
|
})
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load(worker: usize, source: Source) -> Outcome {
|
||||||
|
match source {
|
||||||
|
Source::Local(path) => {
|
||||||
|
let Ok(meta) = std::fs::metadata(&path) else {
|
||||||
|
return Outcome::Missing;
|
||||||
|
};
|
||||||
|
let mtime = meta
|
||||||
|
.modified()
|
||||||
|
.ok()
|
||||||
|
.and_then(|at| at.duration_since(UNIX_EPOCH).ok())
|
||||||
|
.map_or(0, |at| at.as_secs() as i64);
|
||||||
|
if let Some(pixbuf) = cached(&path, mtime) {
|
||||||
|
return texture(&pixbuf);
|
||||||
|
}
|
||||||
|
if meta.len() > MAX_SOURCE_BYTES {
|
||||||
|
return Outcome::Missing;
|
||||||
|
}
|
||||||
|
let Some((pixbuf, scaled)) = decode(&path) else {
|
||||||
|
return Outcome::Missing;
|
||||||
|
};
|
||||||
|
// An image that already fits is its own thumbnail.
|
||||||
|
if scaled && let Err(e) = write_cached(worker, &path, mtime, &pixbuf) {
|
||||||
|
tracing::debug!("cannot cache a thumbnail of {}: {e}", path.display());
|
||||||
|
}
|
||||||
|
texture(&pixbuf)
|
||||||
|
}
|
||||||
|
Source::Drive { mounted, request } => match cached(&mounted, request.modified) {
|
||||||
|
Some(pixbuf) => texture(&pixbuf),
|
||||||
|
None => Outcome::Daemon(request),
|
||||||
|
},
|
||||||
|
Source::Daemon(path) => match gdk::Texture::from_filename(&path) {
|
||||||
|
Ok(texture) => Outcome::Texture(texture),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::debug!("cannot decode {}: {e}", path.display());
|
||||||
|
Outcome::Missing
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `$XDG_CACHE_HOME/thumbnails`.
|
||||||
|
fn thumbnail_root() -> PathBuf {
|
||||||
|
glib::user_cache_dir().join("thumbnails")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The URI and its MD5, which name a file's entry in the thumbnail cache.
|
||||||
|
fn cache_name(path: &Path) -> Option<(String, String)> {
|
||||||
|
let uri = glib::filename_to_uri(path, None).ok()?.to_string();
|
||||||
|
let md5 = glib::compute_checksum_for_data(glib::ChecksumType::Md5, uri.as_bytes())?.to_string();
|
||||||
|
Some((uri, md5))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A thumbnail from the shared cache, if one was made for this version of
|
||||||
|
/// the file.
|
||||||
|
fn cached(path: &Path, mtime: i64) -> Option<Pixbuf> {
|
||||||
|
let (_, md5) = cache_name(path)?;
|
||||||
|
let root = thumbnail_root();
|
||||||
|
["large", "x-large", "normal"].iter().find_map(|size| {
|
||||||
|
let pixbuf = Pixbuf::from_file(root.join(size).join(format!("{md5}.png"))).ok()?;
|
||||||
|
let stamp = pixbuf.option("tEXt::Thumb::MTime")?;
|
||||||
|
(stamp.parse::<i64>().ok()? == mtime).then_some(pixbuf)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode a local image at most [`THUMB_SIZE`] on its longer edge, upright.
|
||||||
|
/// The bool says whether it had to be scaled down.
|
||||||
|
fn decode(path: &Path) -> Option<(Pixbuf, bool)> {
|
||||||
|
let (_, width, height) = Pixbuf::file_info(path)?;
|
||||||
|
let scaled = width > THUMB_SIZE || height > THUMB_SIZE;
|
||||||
|
let pixbuf = if scaled {
|
||||||
|
Pixbuf::from_file_at_scale(path, THUMB_SIZE, THUMB_SIZE, true)
|
||||||
|
} else {
|
||||||
|
Pixbuf::from_file(path)
|
||||||
|
}
|
||||||
|
.ok()?;
|
||||||
|
let pixbuf = pixbuf.apply_embedded_orientation().unwrap_or(pixbuf);
|
||||||
|
Some((pixbuf, scaled))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store a thumbnail as the freedesktop spec asks: in `large/`, with
|
||||||
|
/// `Thumb::URI` and `Thumb::MTime`, readable only by the user, and written
|
||||||
|
/// through a temporary file so another reader never sees half of it.
|
||||||
|
fn write_cached(worker: usize, path: &Path, mtime: i64, pixbuf: &Pixbuf) -> std::io::Result<()> {
|
||||||
|
let (uri, md5) = cache_name(path).ok_or_else(|| std::io::Error::other("no file URI"))?;
|
||||||
|
let dir = thumbnail_root().join("large");
|
||||||
|
std::fs::DirBuilder::new()
|
||||||
|
.recursive(true)
|
||||||
|
.mode(0o700)
|
||||||
|
.create(&dir)?;
|
||||||
|
let mtime = mtime.to_string();
|
||||||
|
let png = pixbuf
|
||||||
|
.save_to_bufferv(
|
||||||
|
"png",
|
||||||
|
&[
|
||||||
|
("tEXt::Thumb::URI", uri.as_str()),
|
||||||
|
("tEXt::Thumb::MTime", mtime.as_str()),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.map_err(std::io::Error::other)?;
|
||||||
|
let tmp = dir.join(format!(
|
||||||
|
"{md5}.png.pdfs-{}-{worker}.tmp",
|
||||||
|
std::process::id()
|
||||||
|
));
|
||||||
|
let written = std::fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.mode(0o600)
|
||||||
|
.open(&tmp)
|
||||||
|
.and_then(|mut file| file.write_all(&png))
|
||||||
|
.and_then(|()| std::fs::rename(&tmp, dir.join(format!("{md5}.png"))));
|
||||||
|
if written.is_err() {
|
||||||
|
let _ = std::fs::remove_file(&tmp);
|
||||||
|
}
|
||||||
|
written
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `Pixbuf` may not leave its thread; its pixels, as a memory texture, can.
|
||||||
|
fn texture(pixbuf: &Pixbuf) -> Outcome {
|
||||||
|
let format = if pixbuf.has_alpha() {
|
||||||
|
gdk::MemoryFormat::R8g8b8a8
|
||||||
|
} else {
|
||||||
|
gdk::MemoryFormat::R8g8b8
|
||||||
|
};
|
||||||
|
let texture = gdk::MemoryTexture::new(
|
||||||
|
pixbuf.width(),
|
||||||
|
pixbuf.height(),
|
||||||
|
format,
|
||||||
|
&pixbuf.read_pixel_bytes(),
|
||||||
|
pixbuf.rowstride() as usize,
|
||||||
|
);
|
||||||
|
Outcome::Texture(texture.upcast())
|
||||||
|
}
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
//! The bundled resources and the optional Proton theme, shared by `pdfs-app`
|
||||||
|
//! and `pdfs-prompt` so both paint the same way from the same preference.
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
use adw::prelude::*;
|
||||||
|
use gtk4::{gio, glib};
|
||||||
|
|
||||||
|
/// Register the bundled GResources (custom icons, the stylesheets) and load
|
||||||
|
/// `stylesheet`, the front end's own sheet. The Proton theme is a separate
|
||||||
|
/// provider, applied by [`set_proton_theme`] from the saved preference.
|
||||||
|
pub(crate) fn load_resources(stylesheet: &str) {
|
||||||
|
let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/pdfs.gresource"));
|
||||||
|
let resource_data = glib::Bytes::from_static(bytes);
|
||||||
|
if let Ok(resource) = gio::Resource::from_data(&resource_data) {
|
||||||
|
gio::resources_register(&resource);
|
||||||
|
} else {
|
||||||
|
tracing::error!("failed to load gresource bundle");
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(display) = gtk4::gdk::Display::default() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let provider = gtk4::CssProvider::new();
|
||||||
|
provider.load_from_resource(stylesheet);
|
||||||
|
// Above the user's own gtk.css, which often imports a whole theme: its
|
||||||
|
// `button { padding: 6px 10px; min-width: 16px }` would otherwise pad every
|
||||||
|
// photo tile and push the grid past the window. Every rule in the sheet is
|
||||||
|
// scoped to the app's own classes, so nothing else the user styled changes.
|
||||||
|
// One above the Proton theme too, so the app's rules refine it.
|
||||||
|
gtk4::style_context_add_provider_for_display(
|
||||||
|
&display,
|
||||||
|
&provider,
|
||||||
|
gtk4::STYLE_PROVIDER_PRIORITY_USER + 2,
|
||||||
|
);
|
||||||
|
gtk4::IconTheme::for_display(&display).add_resource_path("/de/nils/protondrivelinux/icons");
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
/// The Proton theme provider while it is installed. Kept so turning the
|
||||||
|
/// preference off can remove exactly what turning it on added.
|
||||||
|
static PROTON_THEME: RefCell<Option<gtk4::CssProvider>> = const { RefCell::new(None) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the Proton theme is on.
|
||||||
|
pub(crate) fn proton_theme_active() -> bool {
|
||||||
|
PROTON_THEME.with(|slot| slot.borrow().is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// libadwaita's own stylesheet, bundled in the library.
|
||||||
|
const ADWAITA_STYLESHEET: &str = "/org/gnome/Adwaita/styles/gtk.css";
|
||||||
|
|
||||||
|
/// The Proton palette laid over it, from our own bundle.
|
||||||
|
const PROTON_PALETTE: &str = "/de/nils/protondrivelinux/proton-theme.css";
|
||||||
|
|
||||||
|
/// Make the theme provider's `@media (prefers-color-scheme)` and
|
||||||
|
/// `(prefers-contrast)` rules match what libadwaita shows, now and whenever it
|
||||||
|
/// changes. A provider evaluates those itself, and a new one assumes light.
|
||||||
|
///
|
||||||
|
/// The properties are set by name because they only exist since GTK 4.20, and
|
||||||
|
/// the app still runs on older GTK, whose libadwaita has no such rules anyway.
|
||||||
|
fn follow_style_manager(provider: >k4::CssProvider) {
|
||||||
|
fn set_enum(provider: >k4::CssProvider, property: &str, nick: &str) {
|
||||||
|
let Some(pspec) = provider.find_property(property) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(value) = glib::EnumClass::with_type(pspec.value_type())
|
||||||
|
.and_then(|class| class.to_value_by_nick(nick))
|
||||||
|
{
|
||||||
|
provider.set_property_from_value(property, &value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn apply(provider: >k4::CssProvider, manager: &adw::StyleManager) {
|
||||||
|
let scheme = if manager.is_dark() { "dark" } else { "light" };
|
||||||
|
set_enum(provider, "prefers-color-scheme", scheme);
|
||||||
|
let contrast = if manager.is_high_contrast() {
|
||||||
|
"more"
|
||||||
|
} else {
|
||||||
|
"no-preference"
|
||||||
|
};
|
||||||
|
set_enum(provider, "prefers-contrast", contrast);
|
||||||
|
}
|
||||||
|
|
||||||
|
let manager = adw::StyleManager::default();
|
||||||
|
apply(provider, &manager);
|
||||||
|
// Weak, so a provider the preference removed isn't kept alive; its
|
||||||
|
// handlers then do nothing.
|
||||||
|
let weak = provider.downgrade();
|
||||||
|
let weak_dark = weak.clone();
|
||||||
|
manager.connect_dark_notify(move |manager| {
|
||||||
|
if let Some(provider) = weak_dark.upgrade() {
|
||||||
|
apply(&provider, manager);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
manager.connect_high_contrast_notify(move |manager| {
|
||||||
|
if let Some(provider) = weak.upgrade() {
|
||||||
|
apply(&provider, manager);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Paint the window in the Proton web apps' colours (`on`) or follow the
|
||||||
|
/// system theme and accent colour.
|
||||||
|
///
|
||||||
|
/// Setting libadwaita's colour variables alone isn't enough: a GTK theme the
|
||||||
|
/// user's `gtk.css` imports (Catppuccin, Fluent, ...) sits above the app and
|
||||||
|
/// hard-codes its own accent into hundreds of rules. So the theme provider,
|
||||||
|
/// one above the user's CSS, first resets every property, then re-applies
|
||||||
|
/// libadwaita's whole stylesheet and finally the Proton palette, which leaves
|
||||||
|
/// nothing of the user's theme inside the app.
|
||||||
|
pub(crate) fn set_proton_theme(on: bool) {
|
||||||
|
let Some(display) = gtk4::gdk::Display::default() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
PROTON_THEME.with(|slot| {
|
||||||
|
let mut slot = slot.borrow_mut();
|
||||||
|
match (on, slot.as_ref()) {
|
||||||
|
(true, None) => {
|
||||||
|
let stylesheet = |path| {
|
||||||
|
gio::resources_lookup_data(path, gio::ResourceLookupFlags::NONE)
|
||||||
|
.map(|bytes| String::from_utf8_lossy(&bytes).into_owned())
|
||||||
|
};
|
||||||
|
let palette = stylesheet(PROTON_PALETTE).unwrap_or_default();
|
||||||
|
let css = match stylesheet(ADWAITA_STYLESHEET) {
|
||||||
|
// Its images are named relative to the sheet, which a
|
||||||
|
// string loaded here doesn't have.
|
||||||
|
Ok(adwaita) => format!(
|
||||||
|
"* {{ all: unset; }}\n{}\n{palette}",
|
||||||
|
adwaita.replace(
|
||||||
|
"url(\"assets/",
|
||||||
|
"url(\"resource:///org/gnome/Adwaita/styles/assets/"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
// Still recolours everything that follows libadwaita's
|
||||||
|
// variables; only a custom theme's own rules show through.
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("libadwaita stylesheet not found, only recolouring: {e}");
|
||||||
|
palette
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let provider = gtk4::CssProvider::new();
|
||||||
|
follow_style_manager(&provider);
|
||||||
|
provider.load_from_string(&css);
|
||||||
|
gtk4::style_context_add_provider_for_display(
|
||||||
|
&display,
|
||||||
|
&provider,
|
||||||
|
gtk4::STYLE_PROVIDER_PRIORITY_USER + 1,
|
||||||
|
);
|
||||||
|
*slot = Some(provider);
|
||||||
|
}
|
||||||
|
(false, Some(provider)) => {
|
||||||
|
gtk4::style_context_remove_provider_for_display(&display, provider);
|
||||||
|
*slot = None;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,6 +1,13 @@
|
|||||||
# Proton Drive Linux Client: Architecture Specification
|
# Architecture
|
||||||
|
|
||||||
This document provides a deep, comprehensive architectural description of the Proton Drive Linux Client (`pdfs`). It specifies the design patterns, data flows, thread models, and subsystem dependencies that govern the virtual filesystem (FUSE), database persistence, cache management, and two-way sync engine.
|
This document describes how the Proton Drive Linux client (`pdfs`) is built: the crates, the
|
||||||
|
filesystem and its caches, the write queue, the sync engine, the control protocol, the thread
|
||||||
|
layout, what the client writes to disk in plaintext, and the CAPTCHA flow. It is written for
|
||||||
|
contributors and for anyone auditing the client. User-facing documentation starts at
|
||||||
|
[docs/README.md](README.md).
|
||||||
|
|
||||||
|
Numbers quoted here (thread counts, timeouts, limits) are the constants in the source at the time
|
||||||
|
of writing; the source is authoritative when they disagree.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -35,6 +42,11 @@ graph TD
|
|||||||
| [`pdfs-cli`](../crates/pdfs-cli) | Command Line Interface | Command routing, daemon launcher, IPC client wrapper. | Stateless; communicates with daemon over IPC control socket. |
|
| [`pdfs-cli`](../crates/pdfs-cli) | Command Line Interface | Command routing, daemon launcher, IPC client wrapper. | Stateless; communicates with daemon over IPC control socket. |
|
||||||
| [`pdfs-gui`](../crates/pdfs-gui) | Graphical Interface | GTK pages, tray, and the resident quick-search prompt. | Keeps UI state only; all durable state and Drive access remain behind the IPC socket. |
|
| [`pdfs-gui`](../crates/pdfs-gui) | Graphical Interface | GTK pages, tray, and the resident quick-search prompt. | Keeps UI state only; all durable state and Drive access remain behind the IPC socket. |
|
||||||
|
|
||||||
|
The workspace ships four binaries: `pdfs` (the CLI, and the daemon as `pdfs daemon`) from
|
||||||
|
`pdfs-cli`, and `pdfs-app`, `pdfs-tray` and `pdfs-prompt` from `pdfs-gui`. The daemon runs as the
|
||||||
|
systemd user unit `proton-drive.service`. Only the daemon holds the database, the caches and the
|
||||||
|
API client; every other binary is a client of its control socket.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. In-Memory VFS State & File Operations
|
## 2. In-Memory VFS State & File Operations
|
||||||
@@ -105,11 +117,29 @@ A mirror folder is a plain local directory with no FUSE session, so a row can le
|
|||||||
|
|
||||||
## 3. Read Path & Block Caching Pipeline
|
## 3. Read Path & Block Caching Pipeline
|
||||||
|
|
||||||
Read requests are parallelized and served in blocks of size `BLOCK_SIZE` (4 MiB).
|
Reads are served from the revision's content blocks. `Core::read_range` looks in three places
|
||||||
|
before the network:
|
||||||
|
|
||||||
* **Unpinned files (Streaming):** Avoids writing full files to disk to preserve storage. Instead, blocks are kept in a fixed-size `stream_ring` (in-memory ring cache) and evicted immediately.
|
1. **Whole-file blob.** A file kept available offline is downloaded completely into
|
||||||
* **Pinned files (Persistent):** Block downloads are saved directly to `ContentCache` on disk.
|
`ContentCache` and read from there.
|
||||||
* **Read-Ahead:** The reader thread spawns asynchronous tasks to pre-fetch upcoming blocks.
|
2. **Stream ring.** For every other file, a bounded in-memory ring (`RING_BYTES`, 128 MiB) of
|
||||||
|
recently decrypted blocks is checked first, because the kernel reads in pieces far smaller
|
||||||
|
than a block.
|
||||||
|
3. **Block cache.** Blocks are then looked up on disk, under `content/blocks/`. They count
|
||||||
|
against `cache_budget` and are evicted least-recently-used.
|
||||||
|
|
||||||
|
On a miss, only the blocks that overlap the request are fetched, through a `RevisionReader` kept
|
||||||
|
open per revision (`MAX_OPEN_READERS = 64`, validated by `(mtime, size)`).
|
||||||
|
|
||||||
|
* **Block geometry.** Block boundaries come from the revision itself. The first read of a file
|
||||||
|
assumes uniform `BLOCK_SIZE` (4 MiB) blocks; once a reader is open, the real block sizes are
|
||||||
|
recorded (`store_block_geometry`) and later reads plan on them. Proton does not guarantee
|
||||||
|
4 MiB blocks, and assuming it served wrong bytes (`docs/BUGS.md` B85).
|
||||||
|
* **Read-ahead.** A read that proves sequential prefetches a window of 2 to 8 blocks
|
||||||
|
(`PREFETCH_MIN`, `PREFETCH_MAX`). Prefetch takes a permit from a global budget of 8 and gives up
|
||||||
|
instead of queueing, so it never delays a demand read.
|
||||||
|
* **Large videos.** An unpinned video of 256 MiB or more (`STREAM_BYPASS_MIN`) streams without
|
||||||
|
persisting its blocks, so watching a film does not evict the rest of the cache.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
@@ -121,21 +151,25 @@ sequenceDiagram
|
|||||||
participant API as Proton API Client
|
participant API as Proton API Client
|
||||||
|
|
||||||
Kernel->>FUSE: read(ino, fh, offset, size)
|
Kernel->>FUSE: read(ino, fh, offset, size)
|
||||||
FUSE->>FUSE: calculate block indices [first_block..last_block]
|
alt Whole-file blob cached (available offline)
|
||||||
loop For each block index
|
FUSE->>Cache: read blob range
|
||||||
alt Block in disk Cache (Pinned/Cache hit)
|
Cache-->>FUSE: bytes
|
||||||
FUSE->>Cache: read_block(block_idx)
|
else
|
||||||
Cache-->>FUSE: block bytes
|
FUSE->>FUSE: block_geometry() spans overlapping [offset, offset+size)
|
||||||
else Block in stream ring cache
|
loop For each span
|
||||||
FUSE->>Ring: get(block_idx)
|
alt Span in stream ring
|
||||||
|
FUSE->>Ring: get(span)
|
||||||
Ring-->>FUSE: block bytes
|
Ring-->>FUSE: block bytes
|
||||||
else Block Cache Miss
|
else Span in block cache
|
||||||
FUSE->>API: download_range(offset, len)
|
FUSE->>Cache: read_block(span)
|
||||||
|
Cache-->>FUSE: block bytes
|
||||||
|
else Miss
|
||||||
|
FUSE->>API: RevisionReader.read_at(span)
|
||||||
API-->>FUSE: decrypted block bytes
|
API-->>FUSE: decrypted block bytes
|
||||||
alt caching_enabled
|
opt Not a large unpinned video
|
||||||
FUSE->>Cache: store_block(block_idx)
|
FUSE->>Cache: store_block(span)
|
||||||
else streaming_only
|
end
|
||||||
FUSE->>Ring: insert(block_idx)
|
FUSE->>Ring: insert(span)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -220,6 +254,35 @@ graph TD
|
|||||||
5. **Depth-Ascending Batching:** Folders are processed first to ensure hierarchies exist before files are placed. Work is executed concurrently up to a set limit.
|
5. **Depth-Ascending Batching:** Folders are processed first to ensure hierarchies exist before files are placed. Work is executed concurrently up to a set limit.
|
||||||
6. **Safety Gate and Settle:** Destructive plans are rejected when the scan is incomplete. Total-wipe protection applies to every non-empty baseline, including a single-entry folder. On success, baseline entries are upserted, timestamps updated, and any pending mode switches (e.g. going on-demand) are evaluated. Failed local deletion or conflict preservation retains the previous baseline and prevents remote content from overwriting the local source.
|
6. **Safety Gate and Settle:** Destructive plans are rejected when the scan is incomplete. Total-wipe protection applies to every non-empty baseline, including a single-entry folder. On success, baseline entries are upserted, timestamps updated, and any pending mode switches (e.g. going on-demand) are evaluated. Failed local deletion or conflict preservation retains the previous baseline and prevents remote content from overwriting the local source.
|
||||||
|
|
||||||
|
### Conflict Copies
|
||||||
|
|
||||||
|
A conflict keeps both versions. The local file is renamed to
|
||||||
|
`{stem} (sync-conflict {unix_secs}).{ext}` (with `-1`, `-2`, … appended to the timestamp when the
|
||||||
|
name is taken) and the remote version is downloaded under the original name. The conflict sweep
|
||||||
|
(§7) later trashes copies proven byte-identical to their sibling when `conflict_sweep` is
|
||||||
|
`enforce`.
|
||||||
|
|
||||||
|
### Devices and the Machine Profile
|
||||||
|
|
||||||
|
Synced folders live under a *device*: a per-computer root in Proton Drive. `Core::ensure_device`
|
||||||
|
decides which device this machine is: the adopted `device_uid` from `config.json` when set,
|
||||||
|
otherwise a Linux device whose name equals the hostname, otherwise a newly registered one. An
|
||||||
|
adopted UID that no longer exists is reported, never silently replaced.
|
||||||
|
|
||||||
|
Because the local database does not survive a lost machine, the daemon backs up the machine's
|
||||||
|
arrangement to `<device root>/.proton-drive-linux/profile.json` (`pdfs_core::profile`): each synced
|
||||||
|
folder's remote UID, local path and mode, the pins, and the ignore patterns. It goes up the normal
|
||||||
|
upload path and so is end-to-end encrypted like any other file. Mutating requests call
|
||||||
|
`Core::touch_profile`, which coalesces a burst of changes into one upload. `PROFILE_VERSION`
|
||||||
|
guards the format: a reader refuses a profile from a newer version instead of applying part of it.
|
||||||
|
|
||||||
|
Restore (`ListRestorableFolders`, `RestoreSyncFolders`, and the `Device` variants for another
|
||||||
|
computer) lists the device root's folders, proposes a local path from the profile when its parent
|
||||||
|
exists on this machine and `~/<name>` otherwise, and on confirmation adds a `sync_folder` row per
|
||||||
|
folder bound to the remote UID. The download is an ordinary reconcile against an empty baseline;
|
||||||
|
on-demand mode is applied through the normal mode-switch request. The user guide is
|
||||||
|
[RECOVERY.md](RECOVERY.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. IPC Socket Protocol
|
## 6. IPC Socket Protocol
|
||||||
@@ -232,7 +295,31 @@ The CLI and GUI front-ends do not access database files or make network calls di
|
|||||||
* **Control Protocol:**
|
* **Control Protocol:**
|
||||||
* Client sends a single JSON line (`Request`).
|
* Client sends a single JSON line (`Request`).
|
||||||
* Daemon parses, handles the request, and replies with a single JSON line (`Response`).
|
* Daemon parses, handles the request, and replies with a single JSON line (`Response`).
|
||||||
* Timeout durations are separated: **2 seconds** for writes (avoids hangs on defunct sockets) and **120 seconds** for reads (accommodates heavy transfers).
|
* Timeout durations are separated: **2 seconds** for writes (avoids hangs on defunct sockets) and **120 seconds** for reads (accommodates heavy transfers). Status polls use a 5-second read timeout, which is what the app reports as "Not responding".
|
||||||
|
* **Errors:** A failed request replies with `Response::Error { message, kind }`. The `kind` is machine-readable, and `pdfs --json` passes it through.
|
||||||
|
* **Compatibility:** After an upgrade, the old daemon keeps running until the service restarts, so front ends must cope with a daemon that does not know a newer request. It answers such a request with an error instead of dropping the connection. New fields are added with `#[serde(default)]`, and new variants get a separate request rather than an optional field an old daemon would ignore.
|
||||||
|
|
||||||
|
### Push Events
|
||||||
|
|
||||||
|
Since 2.5.0 the front ends do not poll. The app and the tray each send `Request::Subscribe` once
|
||||||
|
and keep the connection open; the daemon then streams one `Event` per line:
|
||||||
|
|
||||||
|
| Event | Sent when | Payload |
|
||||||
|
|---|---|---|
|
||||||
|
| `Status` | On subscribe, and whenever any status field changes | A full `Response::Status` |
|
||||||
|
| `Transfers` | On subscribe, and whenever the transfer snapshot changes | Transfers and background jobs |
|
||||||
|
| `Changed { topics }` | Something a page shows changed | `Topic`s to re-read: `files`, `photos`, `trash`, `shares`, `devices`, `locations`, `queue`, `conflicts`, `activity` |
|
||||||
|
| `Heartbeat` | Every 10 seconds of silence | — |
|
||||||
|
|
||||||
|
Code that changes state publishes its topics to the `EventHub` (`crates/pdfs-fuse/src/events.rs`);
|
||||||
|
the control handler maps each mutating request to the topics it touches. Changes within 150 ms
|
||||||
|
of each other are coalesced into one event. A new subscription is told every topic changed, so it
|
||||||
|
catches up on anything it missed. At most 8 subscriptions are served at once.
|
||||||
|
|
||||||
|
On the client side, `pdfs_core::control::follow` reconnects across daemon restarts with backoff
|
||||||
|
and treats 25 seconds without a line as a stuck daemon. A daemon older than 2.5.0 answers
|
||||||
|
`Subscribe` with an error; `follow` then falls back to polling status and transfers, so the front
|
||||||
|
end sees the same events, only later.
|
||||||
|
|
||||||
### Unified Search
|
### Unified Search
|
||||||
|
|
||||||
@@ -246,12 +333,18 @@ The prompt is a single-instance GTK application. It retains and hides its window
|
|||||||
|
|
||||||
The background daemon relies on the following thread topology:
|
The background daemon relies on the following thread topology:
|
||||||
|
|
||||||
1. **Main Thread / Dispatch Loop:** Blocks on `fuser::Session` loop. Reads kernel FUSE events and hands off network-bound VFS work to the FUSE workers pool.
|
1. **Main Thread / Dispatch Loop:** Blocks on `fuser::Session` loop. Reads kernel FUSE events and hands off network-bound VFS work to the FUSE workers pool. Each on-demand synced folder is a further FUSE session with its own dispatch loop and its own inode space (`State`).
|
||||||
2. **FUSE Workers Pool (11 threads, two lanes):** Bounded thread pool handling network operations. Split into 3 threads reserved for metadata (`lookup`, `readdir`) and 8 general threads that serve transfers (block reads) and fall back to metadata when no transfer is waiting. Reserved threads never accept a transfer — that is what keeps a directory listing from queuing behind saturated downloads (audit A6).
|
2. **FUSE Workers Pool (11 threads, two lanes):** Bounded thread pool handling network operations. Split into 3 threads reserved for metadata (`lookup`, `readdir`) and 8 general threads that serve transfers (block reads) and fall back to metadata when no transfer is waiting. Reserved threads never accept a transfer — that is what keeps a directory listing from queuing behind saturated downloads (audit A6).
|
||||||
3. **IPC listener Thread:** Listens on the Unix socket and admits at most 64 concurrent, timeout-bound handlers for front-end status/configuration requests.
|
3. **Control Threads (`pdfs-control`):** Listen on the Unix socket and admit at most 64 concurrent, timeout-bound handlers. Long-lived subscriptions hold one handler each, up to 8 (see §6).
|
||||||
4. **Sync Engine Loop Thread:** Serializes sync runs. Wakes on debounced local inotify filesystem changes, remote polling intervals, or manual user requests.
|
4. **Sync Engine Threads (`pdfs-sync`, `pdfs-sync-poll`):** Serialize sync passes. Wake on debounced inotify changes, the periodic poll, remote events, or a user request.
|
||||||
5. **Drain Queue Worker Thread:** Processes staged writes (`PendingOp`) sequentially, uploading revisions and retrying with exponential backoff on failures.
|
5. **Drain Workers (`pdfs-drain-0` … `pdfs-drain-2`):** `DRAIN_WORKERS = 3` threads share the queue of staged writes and changes (`PendingOp`), with per-node ordering (§4), and retry failures with exponential backoff. Worker 0 also runs the queue's idle chores.
|
||||||
6. **Conflict Sweep Thread (`pdfs-conflict-sweep`, optional):** Reconciles leftover `(sync-conflict …)` copies — 30 s warmup, then one pass every 5 minutes. A copy proven identical to its live sibling (equal size **and** equal `content_sha1`) is redundant; anything it cannot prove identical is surfaced to the activity feed and left alone. Governed by `AppConfig.conflict_sweep` / `PDFS_CONFLICT_SWEEP` (`SweepMode`): **report-only by default**, `off` skips the thread entirely, and only `enforce` lets it trash. Because it deletes, the enforcing path re-verifies revision id, size, digest, name, parent, queued ops and open handles immediately before acting rather than trusting its own pass-start snapshot. See `docs/BUGS.md` B69 and B71.
|
6. **Conflict Sweep Thread (`pdfs-conflict-sweep`, optional):** Reconciles leftover `(sync-conflict …)` copies — 30 s warmup, then one pass every 5 minutes. A copy proven identical to its live sibling (equal size **and** equal `content_sha1`) is redundant; anything it cannot prove identical is surfaced to the activity feed and left alone. Governed by `AppConfig.conflict_sweep` / `PDFS_CONFLICT_SWEEP` (`SweepMode`): **report-only by default**, `off` skips the thread entirely, and only `enforce` lets it trash. Because it deletes, the enforcing path re-verifies revision id, size, digest, name, parent, queued ops and open handles immediately before acting rather than trusting its own pass-start snapshot. See `docs/BUGS.md` B69 and B71.
|
||||||
|
7. **Remote Event Tasks (Tokio):** `run_event_sync` follows Proton's event stream for the Drive volume and applies each change to every mounted inode space, invalidating kernel entries as it goes. The cursor is persisted after every batch, so changes made while the daemon was stopped are applied on the next start. `run_photos_event_sync` follows the photos volume separately; it only drops gallery rows or the timeline's freshness stamp, since photos are not part of the mount.
|
||||||
|
8. **Event Sampler (`pdfs-events`):** Publishes status, transfer and sync-progress changes to subscribed front-ends (§6).
|
||||||
|
9. **Supervisor (`pdfs-supervisor`):** Logs workers that hold one job for minutes, a queue that stops draining and control requests that never return; samples memory; and pings the systemd watchdog only after a real round trip over the control socket, so a hung daemon is restarted (`WatchdogSec=120`). `pdfs diagnostics` reports the same data on demand.
|
||||||
|
10. **Smaller helpers:** `pdfs-online-probe` (connectivity), `pdfs-localindex` (home-directory index for the search prompt), `pdfs-pause` (ends a timed pause), `pdfs-conflict-sweep`, and `pdfs-similar` (duplicate-photo detection, on request).
|
||||||
|
|
||||||
|
Every long-lived loop waits on one shared `Shutdown` signal instead of sleeping, so an in-process remount joins the old generation of threads instead of leaking it (`docs/BUGS.md` B44).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -377,3 +470,100 @@ sequenceDiagram
|
|||||||
This ensures compatibility with all versions of Proton's client verification scripts.
|
This ensures compatibility with all versions of Proton's client verification scripts.
|
||||||
3. **SRP Handshake Reset:** Because a gated login burns the SRP handshake on the API side, the client cannot simply resume the previous request. Instead, `auth::login_interactive` restarts the SRP process from scratch with the verification credentials attached, keeping the complex handshake details isolated from the front-end.
|
3. **SRP Handshake Reset:** Because a gated login burns the SRP handshake on the API side, the client cannot simply resume the previous request. Instead, `auth::login_interactive` restarts the SRP process from scratch with the verification credentials attached, keeping the complex handshake details isolated from the front-end.
|
||||||
4. **CLI Fallback:** Since the CLI has no native web browser engine, hitting the CAPTCHA gate fails immediately with a user-friendly message directing the user to sign in once via the GUI (`pdfs-app`) to persist the authenticated session keys to the system keyring.
|
4. **CLI Fallback:** Since the CLI has no native web browser engine, hitting the CAPTCHA gate fails immediately with a user-friendly message directing the user to sign in once via the GUI (`pdfs-app`) to persist the authenticated session keys to the system keyring.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Feature Design Notes
|
||||||
|
|
||||||
|
Notes on individual features whose code is shaped by a constraint that is not obvious from the
|
||||||
|
code alone.
|
||||||
|
|
||||||
|
### File Version History
|
||||||
|
**Files**: [`revisions.rs`](../crates/pdfs-fuse/src/revisions.rs), [`control.rs`](../crates/pdfs-core/src/control.rs), [`versions_dialog.rs`](../crates/pdfs-gui/src/app/widgets/versions_dialog.rs)
|
||||||
|
|
||||||
|
Proton Drive keeps every revision a client committed; the daemon only ever addressed the active one, so a file overwritten by a sync pass could be recovered only from whatever the local `recovery/` directory happened to hold.
|
||||||
|
|
||||||
|
The control protocol gained `ListRevisions` / `RestoreRevision` / `DeleteRevision` / `SaveRevisionAs` (each with a `…ByUid` twin for nodes the primary mount cannot name), the CLI gained `pdfs versions list|restore|save|rm`, and the browser's details pane gained a **Versions** button opening a per-file dialog.
|
||||||
|
|
||||||
|
Three properties that shape the code:
|
||||||
|
|
||||||
|
- **A restore is server-side and asynchronous.** No content crosses the wire and nothing enters the drain queue; the server answers 202 and swaps the active revision in the background, so the daemon evicts the file's cached blocks and open readers rather than describing the new state, and the UI wording promises the request, not the result.
|
||||||
|
- **The active revision cannot be deleted.** `Core::delete_revision_for_uid` checks that locally so the user gets a sentence rather than an API code, and the dialog gives the current row no delete button.
|
||||||
|
- **Saving a version never overwrites.** `SaveRevisionAs` refuses an existing destination and removes a partial file if the download fails — a half-written export looks identical to a good one to every tool that opens it afterwards.
|
||||||
|
|
||||||
|
### Photo Favorites
|
||||||
|
**Files**: [`photos.rs`](../crates/pdfs-fuse/src/photos.rs), [`photo_viewer.rs`](../crates/pdfs-gui/src/app/pages/photo_viewer.rs)
|
||||||
|
|
||||||
|
The gallery can mark and filter favorites, through the SDK's photo-tag API (`update_photos`). Schema **v20** adds `photos.favorite`; the flag is learned in the timeline enrichment pass that already resolves each photo's name and media type, and is kept across a refresh that could not resolve a photo — the same learned-and-kept rule as `media_type`. The lightbox carries a star toggle, the Photos header a favorites filter, and the CLI has `pdfs favorite <uid> [--remove]` plus `pdfs photos --favorites`.
|
||||||
|
|
||||||
|
Favouriting a photo that is not on this account's own photos volume (shared with us, or album-only) needs it re-encrypted for our timeline root, which the SDK does not implement; the daemon surfaces that as an error rather than silently doing nothing.
|
||||||
|
|
||||||
|
### Justified Gallery, Gallery Delete
|
||||||
|
**Files**: [`photos.rs`](../crates/pdfs-gui/src/app/pages/photos.rs), [`photo_viewer.rs`](../crates/pdfs-gui/src/app/pages/photo_viewer.rs), [`photos.rs`](../crates/pdfs-fuse/src/photos.rs), [`background.rs`](../crates/pdfs-fuse/src/background.rs)
|
||||||
|
|
||||||
|
The gallery lays each day out in justified rows (`justify_rows` / `plan_rows` / `fit_row`): photos are taken in capture order until their summed aspect ratio no longer fits the target row height, and the row is then scaled so it ends exactly on the content width. A tile is therefore its own photo's shape, and `ContentFit::Contain` has nothing left to crop. The last row of a day is left at the target height rather than stretched, because a day holding two photos is a short day, not a layout fault.
|
||||||
|
|
||||||
|
- **Ratios that are not known yet.** `PhotoItem::ratio` is persisted, but a photo that has never been decoded has none. Those are laid out square and remembered in `assumed_ratios`; when a decode proves the real shape the day re-flows on the existing debounce timer, so a screenful of decodes costs one re-flow rather than one per photo. Ratios are clamped to 0.4–3.0 so a single panorama cannot decide what a row looks like.
|
||||||
|
- **Thumbnails survive recycling.** A reply for a tile that has already been re-bound still lands in the texture cache, which is an LRU of 1500 textures keyed by uid, and the rows just outside the realised range are prefetched in the scroll direction.
|
||||||
|
- **Delete is uid-addressed.** The photos volume is not in the FUSE mount, so the path-based `Request::Delete` cannot reach it. `Request::TrashNodes { uids }` trashes through the SDK and answers `Response::Trashed { trashed, failed }`; the daemon then drops those rows with `Db::photos_delete` (album membership included) so the gallery does not wait for a timeline refresh. The GUI removes the tiles optimistically with an Undo toast and puts back anything the server refused.
|
||||||
|
- **Remote deletions arrive on their own.** The Drive volume and the photos volume have separate event streams, so `run_photos_event_sync` polls the photos volume with its own cursor (`photos_event_cursor` in the state table). Trash and delete events remove rows; anything else only invalidates freshness, because there is no inode space to converge on this volume. Together with a 60 s `TIMELINE_TTL` and a `RefreshScope::Photos` that now awaits the refresh, a photo deleted on a phone leaves the grid in about ten seconds.
|
||||||
|
|
||||||
|
### RAW + JPEG Grouping
|
||||||
|
**Files**: [`photos.rs`](../crates/pdfs-core/src/db/photos.rs), [`migrations.rs`](../crates/pdfs-core/src/db/migrations.rs), [`photos.rs`](../crates/pdfs-fuse/src/photos.rs), [`photo_viewer.rs`](../crates/pdfs-gui/src/app/pages/photo_viewer.rs)
|
||||||
|
|
||||||
|
Schema **v30** adds `content_hash`, `main_uid` and an indexed `group_key` to `photos`. The first two come from the server's `PhotoProperties` and are filled by the enrichment pass in `refresh_timeline` that already resolves each photo's name, media type and favorite tag; `group_key` is computed in `photos_replace`, where the whole timeline is in hand.
|
||||||
|
|
||||||
|
- **Precedence.** The server relation first (`main_photo_uid` and `related_photo_uids`, read from whichever end resolves — the two can land in different enrichment chunks); then same capture day, same case-insensitive name stem, and one member raw while the other is not; otherwise the photo is its own group. The union-find pass is over the timeline in memory, so it costs one pass per refresh rather than a query per photo.
|
||||||
|
- **Representative.** The non-raw member when there is one — a JPEG decodes in milliseconds and is what the person expects to see. `group_key` holds that member's uid, so "is this the tile" is `uid = group_key`, an index scan.
|
||||||
|
- **What is grouped and what is not.** `photos_page`, `photos_months` and `photos_counts` count groups, except on the Raw tab, which lists files. Album pages are never grouped: an album is a list someone made. The relation is learned-and-kept like `media_type`, so a refresh that could not resolve a node does not break a group up.
|
||||||
|
- **Deleting.** `Core::trash_photos` expands each uid to its group before trashing, because the grid shows the shot as one tile — leaving the RAW behind would put the photo back on the Raw tab and nowhere else. The GUI's confirmation names the file count.
|
||||||
|
- **The other files.** `Request::PhotoGroup { uid }` answers with the group's members as ordinary `PhotoItem`s, which is what the lightbox's switch steps through.
|
||||||
|
|
||||||
|
### Persistent SDK Entity Cache
|
||||||
|
**Files**: [`sdkcache.rs`](../crates/pdfs-core/src/sdkcache.rs), [`auth.rs`](../crates/pdfs-core/src/auth.rs), [`background.rs`](../crates/pdfs-fuse/src/background.rs)
|
||||||
|
|
||||||
|
The SDK's Drive entity cache (decrypted node metadata: name, size, parent, signing share) defaulted to memory, so every daemon restart re-fetched and re-decrypted the tree the previous run had already walked. It is now backed by SQLite.
|
||||||
|
|
||||||
|
- **Its own file** (`sdk_cache.db` in the state directory), not `cache.db`: the daemon's `Db` is one `Mutex<Connection>` shared by every FUSE thread and the control socket, and this traffic is frequent, small and entirely reconstructible. A separate file also means no schema migration — the store can be deleted at any time.
|
||||||
|
- **Encrypted at rest** by wrapping it in the SDK's `EncryptedCacheRepository`, keyed by the mailbox password. A password change reads as a cold cache (the SDK treats an undecryptable entry as a miss and clears the store), not as an error.
|
||||||
|
- **Staleness** is closed by the event loop, which already replays from a persisted cursor and calls `invalidate_caches_for_event` for every event — including those raised while the daemon was down. The one case with no trail is a *seeded* cursor (first-ever mount, or a lost cursor), where the seed path now clears the store instead of trusting it. `auth::logout` removes the file.
|
||||||
|
|
||||||
|
Needed a small additive SDK change (`ProtonDriveClient::with_entity_repository`, 0.5.1): `with_entity_cache` is a constructor and so could not be combined with `with_key_salts`, which the daemon requires.
|
||||||
|
|
||||||
|
### Per-node Batch Outcomes
|
||||||
|
**File**: [`batch.rs`](../crates/pdfs-core/src/batch.rs)
|
||||||
|
|
||||||
|
SDK 0.5.0 made `trash_nodes` / `restore_nodes` / `delete_nodes` report one outcome per node instead of failing the whole call (mirroring upstream's streamed `NodeActionResult`). `pdfs_core::batch::into_unit` collapses the single-node calls back to a `Result`; `batch::split` handles a collected batch.
|
||||||
|
|
||||||
|
A restore is expanded before it is sent (`expand_restore`, `pdfs-fuse/src/lib.rs`): the persisted trash listing carries each row's `parent_uid` (schema 29), so a restore takes the connected piece of the trashed tree — every trashed descendant of what was asked for, and every trashed ancestor above it — and sends it shallowest wave first, because the server cannot put a node back under a parent that is still trashed. A uid the listing does not know about (it is materialised in chunks, and can be stale) is restored on its own rather than dropped.
|
||||||
|
|
||||||
|
The trash view's restore and permanent-delete use the SDK's **streaming** variants (`restore_nodes_streaming` / `delete_nodes_streaming`) and apply local state per node as each batch lands rather than after the last one. For a permanent delete — which is irreversible — that means a daemon interrupted mid-batch has forgotten exactly the nodes the server destroyed, no more and no fewer. Both report per-node failures and count only what actually succeeded; only a batch where nothing succeeded is an error.
|
||||||
|
|
||||||
|
### Open-for-Write Deferral for Mirror Sync
|
||||||
|
**File**: [`sync.rs`](../crates/pdfs-fuse/src/sync.rs)
|
||||||
|
|
||||||
|
The mirror folder sync engine now defers uploading any file that is currently held open for writing by another process, matching the guarantee the FUSE mount path already provides (where uploads are deferred until `close(fd)`).
|
||||||
|
|
||||||
|
**Problem**: Previously, the mirror sync path relied solely on a 2-second trailing-edge debounce (with a 30-second ceiling) to avoid uploading files mid-write. This was insufficient for slow continuous writers (e.g., database dumps, large exports, or editors that keep files open for extended periods), which could have their incomplete state uploaded as a real revision.
|
||||||
|
|
||||||
|
**Solution**: Before each reconcile pass, the sync engine scans `/proc/*/fd` once to build a set of canonical paths within the sync root that any process holds open for writing (`O_WRONLY` or `O_RDWR`). Files in this set are:
|
||||||
|
|
||||||
|
- **Kept in the local walk** — so they are not misclassified as deletions
|
||||||
|
- **Treated as unchanged** — so no upload is queued for them
|
||||||
|
- **Counted as `deferred`** — so the activity summary reports them (e.g., "3 uploaded, 1 deferred (open for write)")
|
||||||
|
- **Picked up on the next pass** — after the writer has closed the file
|
||||||
|
|
||||||
|
**How it works**:
|
||||||
|
1. `open_for_write_set(root)` reads `/proc/*/fd` → `readlink` for each fd → prefix-checks against the sync root → reads `/proc/<pid>/fdinfo/<n>` to check the `flags:` line's low two bits (O_ACCMODE)
|
||||||
|
2. `walk_local` sets `LocalItem.open_for_write = true` for matching files
|
||||||
|
3. Both `do_reconcile` and `push_pass` skip upload classification for flagged files
|
||||||
|
4. `Outcome.deferred` tracks the count for the activity summary
|
||||||
|
|
||||||
|
**Performance**: The `/proc` scan completes in low single-digit milliseconds on a typical desktop. Only fds whose `readlink` target falls under the sync root incur the `fdinfo` read.
|
||||||
|
|
||||||
|
**Tests added**: 8 unit tests covering `is_write_mode` parsing of various fdinfo flag combinations, and `Outcome` summary formatting with deferred counts.
|
||||||
|
|
||||||
|
| Sync Path | Open-for-write protection | Mechanism |
|
||||||
|
|---|---|---|
|
||||||
|
| FUSE mount | Yes | Upload deferred until `close(fd)` |
|
||||||
|
| Mirror folders | Yes | `/proc/*/fd` scan per reconcile pass |
|
||||||
@@ -11,6 +11,55 @@ scratch (user data in `staging/` and `recovery/` is never touched by this).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [2.6.0] — 2026-09-28
|
||||||
|
|
||||||
|
The search launcher (`pdfs-prompt`) is rebuilt around a preview pane, and it now looks like the
|
||||||
|
app it belongs to.
|
||||||
|
|
||||||
|
Schema: **35** (unchanged).
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Launcher preview pane.** The selected result shows a large thumbnail, its type, size,
|
||||||
|
modified date, location and whether it is on this computer, with Open (Enter), Show in Folder
|
||||||
|
(Ctrl+Enter) and Copy Path (Ctrl+C, or Ctrl+Shift+C while text is selected in the search box).
|
||||||
|
Show in Folder selects the file in the file manager through `org.freedesktop.FileManager1`
|
||||||
|
and falls back to opening the folder.
|
||||||
|
- **Recent files.** With an empty search box, the launcher lists the last files opened from it
|
||||||
|
above the pinned ones. Files opened often and lately also rank higher in search results. The
|
||||||
|
history lives in `prompt-history.json` in the state directory and forgets local files that
|
||||||
|
no longer exist.
|
||||||
|
- **Thumbnails in the launcher.** Images show a thumbnail in the result list and the preview,
|
||||||
|
from the desktop's thumbnail cache, decoded locally (and written back to that cache), or
|
||||||
|
fetched by the daemon for Drive files.
|
||||||
|
- **Drag and drop from the launcher.** A result can be dragged into a file manager or another
|
||||||
|
application as a file.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **The launcher follows the app's theme.** It uses the system accent colour, light and dark
|
||||||
|
style, and the opt-in Proton theme, instead of always pinning Proton purple. Changes to the
|
||||||
|
theme or to `open_with` apply the next time the launcher opens.
|
||||||
|
- **Launcher layout.** Results are grouped into Recent, Pinned in Proton Drive and Best matches;
|
||||||
|
matched characters are bold, and each row shows its sync state, size and age. A click selects
|
||||||
|
a row and a double-click opens it; the search box keeps the keyboard focus throughout.
|
||||||
|
- **Faster search as you type.** The launcher searches 80 ms after the last keystroke instead
|
||||||
|
of 120 ms.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **The launcher no longer freezes on a stuck mount.** Checking whether a Drive file is in the
|
||||||
|
mount runs off the main thread and gives up after 2 seconds, falling back to the daemon.
|
||||||
|
Escape cancels a Drive file that is still opening.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- **Rewritten user documentation.** New pages for installation, the user guide, the command-line
|
||||||
|
reference, configuration and troubleshooting, and a documentation index in `docs/README.md`.
|
||||||
|
The README is shorter and points to them.
|
||||||
|
- **Recovery** describes the current restore flow (adopting a computer, `pdfs sync restore`, the
|
||||||
|
machine profile) instead of the old manual runbook.
|
||||||
|
- **Architecture** covers push events, remote event streams, the revision block geometry, the
|
||||||
|
thread layout of 2.5.0 and the machine profile. The development page is now a developer guide,
|
||||||
|
and open work moved to `docs/ROADMAP.md`.
|
||||||
|
- `scripts/publish-wiki.sh` publishes the user pages to the GitHub wiki.
|
||||||
|
|
||||||
## [2.5.0] — 2026-09-28
|
## [2.5.0] — 2026-09-28
|
||||||
|
|
||||||
A polish pass over the desktop app: the daemon pushes changes instead of being polled, pages
|
A polish pass over the desktop app: the daemon pushes changes instead of being polled, pages
|
||||||
@@ -320,7 +369,7 @@ Schema: **31** (unchanged).
|
|||||||
opened in between.
|
opened in between.
|
||||||
- **Photo viewer rewrite.** The lightbox's controls fade out after two seconds without pointer
|
- **Photo viewer rewrite.** The lightbox's controls fade out after two seconds without pointer
|
||||||
movement. Photos zoom with Ctrl+scroll, a pinch, `+`/`-`, `1` for 100 % and double-click, and
|
movement. Photos zoom with Ctrl+scroll, a pinch, `+`/`-`, `1` for 100 % and double-click, and
|
||||||
pan by dragging; `0` fits them again. The details panel docks beside the photo instead of
|
pan by dragging; `0` fits them again. The details panel docks beside the photo instead of 120 ms
|
||||||
covering it. Stepping past the last loaded photo loads the next page, and the counter shows the
|
covering it. Stepping past the last loaded photo loads the next page, and the counter shows the
|
||||||
library's total rather than the loaded count. A video plays inline. Save a Copy uses the photo's
|
library's total rather than the loaded count. A video plays inline. Save a Copy uses the photo's
|
||||||
real name and reports failures, and Open With… offers the app chooser.
|
real name and reports failures, and Open With… offers the app chooser.
|
||||||
@@ -658,7 +707,7 @@ thumbnail.
|
|||||||
slider for the icon grid, and account storage usage. Zoom resizes the realised grid cells rather
|
slider for the icon grid, and account storage usage. Zoom resizes the realised grid cells rather
|
||||||
than rebuilding the model, so dragging the slider does not tear down the selection; the default
|
than rebuilding the model, so dragging the slider does not tear down the selection; the default
|
||||||
size reproduces the previous fixed 72 px tiles. Capacity stays out of the bar until a real
|
size reproduces the previous fixed 72 px tiles. Capacity stays out of the bar until a real
|
||||||
reading lands, and the quota fetch (60 s TTL) now also runs while Files is on screen instead of
|
reading lands, and the quota fetch (60 s TTL) now also runs while Files is on screen instead of 120 ms
|
||||||
only in Settings.
|
only in Settings.
|
||||||
- Repository housekeeping: `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, issue and pull
|
- Repository housekeeping: `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `SECURITY.md`, issue and pull
|
||||||
request templates, a project logo, and workspace crate metadata (`repository`, `homepage`,
|
request templates, a project logo, and workspace crate metadata (`repository`, `homepage`,
|
||||||
@@ -1177,8 +1226,9 @@ First stable release: FUSE files-on-demand mount, sync daemon under `proton-driv
|
|||||||
- The outstanding FUSE defects tracked in `docs/BUGS.md`, plus a truncate defect, validated
|
- The outstanding FUSE defects tracked in `docs/BUGS.md`, plus a truncate defect, validated
|
||||||
by a new POSIX compliance suite for the filesystem.
|
by a new POSIX compliance suite for the filesystem.
|
||||||
|
|
||||||
|
[2.6.0]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.6.0
|
||||||
|
[2.5.0]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.5.0
|
||||||
[2.4.0]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.4.0
|
[2.4.0]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.4.0
|
||||||
|
|
||||||
[2.3.0]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.3.0
|
[2.3.0]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.3.0
|
||||||
[2.2.2]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.2.2
|
[2.2.2]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.2.2
|
||||||
[2.2.1]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.2.1
|
[2.2.1]: https://github.com/narrrl/proton-drive-linux/releases/tag/v2.2.1
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
# Command-line reference
|
||||||
|
|
||||||
|
`pdfs` controls the Proton Drive service from a terminal and from scripts. Almost every command
|
||||||
|
talks to the running service (`proton-drive.service`); `login`, `logout`, `mount`, `daemon` and
|
||||||
|
`diagnose` work without it.
|
||||||
|
|
||||||
|
`pdfs <command> --help` prints the full help of any command. This page groups them by task.
|
||||||
|
|
||||||
|
- [Conventions](#conventions)
|
||||||
|
- [Account and service](#account-and-service)
|
||||||
|
- [Files](#files)
|
||||||
|
- [Available offline](#available-offline)
|
||||||
|
- [Synced folders](#synced-folders)
|
||||||
|
- [Conflicts](#conflicts)
|
||||||
|
- [Computers](#computers)
|
||||||
|
- [Trash](#trash)
|
||||||
|
- [Versions](#versions)
|
||||||
|
- [Sharing](#sharing)
|
||||||
|
- [Photos](#photos)
|
||||||
|
- [Activity and transfers](#activity-and-transfers)
|
||||||
|
- [Maintenance and diagnostics](#maintenance-and-diagnostics)
|
||||||
|
- [Scripting with `--json`](#scripting-with---json)
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- **Paths** name items in My files. They may be absolute paths inside the Proton Drive folder
|
||||||
|
(`~/ProtonDrive/Documents/a.pdf`) or relative to it (`Documents/a.pdf`).
|
||||||
|
- **UIDs** identify items that have no path in the mount: photos, trashed items, shared items and
|
||||||
|
computers. The listing commands print them.
|
||||||
|
- **Exit codes**: `0` on success, non-zero on any failure, including an error reported by the
|
||||||
|
service.
|
||||||
|
- **Output** is English and stable enough for `grep`; use `--json` where it is available.
|
||||||
|
|
||||||
|
## Account and service
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `pdfs login [-u EMAIL]` | Sign in (password, then a 2FA code if the account has one) and store the session in the system keyring. Restarts the service if it is running. |
|
||||||
|
| `pdfs logout` | Remove the stored session. |
|
||||||
|
| `pdfs status` | Account, mount, queue and cache state. |
|
||||||
|
| `pdfs quota` | Storage used across all Proton products. |
|
||||||
|
| `pdfs locations` | Every local place the service occupies: the Proton Drive folder and each synced folder, with mode and state. |
|
||||||
|
| `pdfs daemon [MOUNTPOINT]` | Run the service in the foreground: wait for a session, mount, stay mounted. `proton-drive.service` runs this. |
|
||||||
|
| `pdfs mount [MOUNTPOINT]` | Mount once and block until unmounted. Defaults to `~/ProtonDrive`. |
|
||||||
|
|
||||||
|
`pdfs login` cannot show a CAPTCHA. If Proton asks for one, sign in with `pdfs-app` instead.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `pdfs ls [PATH]` | List a folder. |
|
||||||
|
| `pdfs search QUERY [--in FOLDER] [--limit N]` | Search names in the local index. |
|
||||||
|
| `pdfs mkdir PARENT NAME` | Create a folder. |
|
||||||
|
| `pdfs rename PATH NEW_NAME` | Rename a file or folder. |
|
||||||
|
| `pdfs move PATH NEW_PARENT` | Move into another folder. |
|
||||||
|
| `pdfs rm PATH` | Move to the Trash. |
|
||||||
|
| `pdfs upload SOURCES... [-t FOLDER]` | Upload local files and folders (recursively) in the background. Follow with `pdfs transfers`. |
|
||||||
|
| `pdfs refresh [TARGET] [--full]` | Forget a cached listing so the next read fetches it again. `TARGET` is a folder, `trash` or `photos`; `--full` with `photos` re-reads every photo's metadata. |
|
||||||
|
|
||||||
|
Ordinary file tools (`cp`, `mv`, `rsync`, editors) work on the Proton Drive folder directly. These
|
||||||
|
commands are for scripts and for cases where the mount is not available.
|
||||||
|
|
||||||
|
## Available offline
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `pdfs pin PATH` | Download a file completely and keep it on this computer. |
|
||||||
|
| `pdfs unpin PATH` | Release it back to online only. |
|
||||||
|
| `pdfs pins` | List files kept available offline. |
|
||||||
|
|
||||||
|
## Synced folders
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `pdfs sync add PATH` | Back up a local folder under this computer and keep it in sync. |
|
||||||
|
| `pdfs sync list` | List synced folders with their IDs and state. |
|
||||||
|
| `pdfs sync rm ID [--delete-remote]` | Stop syncing. Local files stay; `--delete-remote` also removes the copy on Proton Drive. |
|
||||||
|
| `pdfs sync mode ID mirror\|ondemand` | Switch between a full local copy (`mirror`) and online only (`ondemand`). |
|
||||||
|
| `pdfs sync now [ID]` | Start a sync pass now, for all folders or one. |
|
||||||
|
| `pdfs sync pause [--for 1h] [--folder ID]` | Pause uploads and synced folders. Reading files keeps working. `--for` ends the pause by itself (`30m`, `1h`, `2d`). |
|
||||||
|
| `pdfs sync resume [--folder ID]` | Resume after a pause. |
|
||||||
|
| `pdfs sync limit [--up RATE] [--down RATE]` | Cap bandwidth, for example `500K` or `2M` per second. `0` removes a cap. Without options, prints the current caps. |
|
||||||
|
| `pdfs sync queue` | List changes that have not reached Proton Drive yet. |
|
||||||
|
| `pdfs sync retry [ID]` | Retry one queued operation, or every failed one. |
|
||||||
|
| `pdfs sync restore [--device UID] [--yes]` | Re-attach this computer's backed-up folders to local folders and download them. `--device` restores another computer's folders. See [RECOVERY.md](RECOVERY.md). |
|
||||||
|
|
||||||
|
## Conflicts
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `pdfs conflicts` | List `(sync-conflict …)` copies. |
|
||||||
|
| `pdfs conflicts resolve PATH --keep original\|copy\|both [--name NAME]` | `original` trashes the copy; `copy` trashes the original and gives the copy its name; `both` renames the copy to `--name`. |
|
||||||
|
|
||||||
|
## Computers
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `pdfs devices list` | List computers registered to the account. |
|
||||||
|
| `pdfs devices rename UID NAME` | Rename a computer. |
|
||||||
|
| `pdfs devices rm UID` | Remove a computer and its backup from the account. |
|
||||||
|
| `pdfs devices adopt UID` | Make this machine continue another computer's backup, for example after a reinstall. `--clear` returns to matching by hostname. |
|
||||||
|
|
||||||
|
## Trash
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `pdfs trash` | List the Trash with the UIDs the next commands take. |
|
||||||
|
| `pdfs restore UIDS...` | Put items back where they were. |
|
||||||
|
| `pdfs delete-forever UIDS...` | Delete items permanently. |
|
||||||
|
| `pdfs empty-trash` | Delete everything in the Trash permanently. |
|
||||||
|
|
||||||
|
## Versions
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `pdfs versions list PATH` | List a file's versions, newest first. |
|
||||||
|
| `pdfs versions restore PATH ID` | Make an earlier version current. Runs on the server; nothing is re-uploaded. |
|
||||||
|
| `pdfs versions save PATH ID DEST` | Write an earlier version to a local file. Refuses to overwrite `DEST`. |
|
||||||
|
| `pdfs versions rm PATH ID` | Delete an earlier version permanently. The current version cannot be deleted. |
|
||||||
|
|
||||||
|
## Sharing
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `pdfs share PATH EMAILS... [--role viewer\|editor\|admin] [--message TEXT]` | Invite people to a file or folder. |
|
||||||
|
| `pdfs members PATH` | List members, pending invitations and the public link. |
|
||||||
|
| `pdfs share-role PATH ID KIND ROLE` | Change a member's or invitation's role. |
|
||||||
|
| `pdfs unshare PATH [ID KIND]` | Stop sharing, or remove one member or invitation. |
|
||||||
|
| `pdfs public-link create PATH [--role] [--password] [--expires EPOCH]` | Create a public link. `--expires` takes Unix seconds. |
|
||||||
|
| `pdfs public-link remove PATH ID` | Remove a public link. |
|
||||||
|
| `pdfs shared` | List what you share. |
|
||||||
|
| `pdfs shared-with-me [UID]` | List what others share with you, or the contents of one shared folder. |
|
||||||
|
| `pdfs shared-get UID [DEST]` | Download a file shared with you. |
|
||||||
|
| `pdfs leave UID` | Leave a share. |
|
||||||
|
| `pdfs invitations list\|accept ID\|reject ID` | Manage invitations addressed to you. |
|
||||||
|
| `pdfs bookmarks list\|add URL [--password]\|rm TOKEN` | Manage saved public links. |
|
||||||
|
|
||||||
|
## Photos
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `pdfs photos [--limit N] [--offset N] [--favorites]` | List the timeline, newest first. |
|
||||||
|
| `pdfs open-photo UID` | Download a photo and print its cached path. |
|
||||||
|
| `pdfs favorite UID [--remove]` | Mark or unmark a favorite. |
|
||||||
|
| `pdfs albums` | List albums, including those shared with you. |
|
||||||
|
| `pdfs album UID` | List an album's photos. |
|
||||||
|
| `pdfs new-album NAME` | Create an album and print its UID. |
|
||||||
|
| `pdfs rename-album UID NAME` | Rename an album. |
|
||||||
|
| `pdfs delete-album UID` | Delete an album. Its photos stay in the timeline. |
|
||||||
|
| `pdfs add-to-album UID PHOTOS...` | Add photos to an album. |
|
||||||
|
| `pdfs remove-from-album UID PHOTOS...` | Take photos out of an album. |
|
||||||
|
| `pdfs import-google-photos ARCHIVES... [--dry-run] [--wait]` | Import a Google Takeout export. Pass every `.zip` of the export. |
|
||||||
|
| `pdfs import-status` / `pdfs cancel-import` | Follow or stop a running import. |
|
||||||
|
| `pdfs redate-photos [--dry-run] [--from DATE] [--to DATE] [--limit N] [--wait]` | Fix photos filed under their upload day when the file name carries the real date. |
|
||||||
|
| `pdfs redate-status` / `pdfs cancel-redate` | Follow or stop a running re-date. |
|
||||||
|
|
||||||
|
## Activity and transfers
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `pdfs activity [-l N]` | Recent activity, newest first. |
|
||||||
|
| `pdfs transfers` | Uploads and downloads in progress. |
|
||||||
|
|
||||||
|
## Maintenance and diagnostics
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `pdfs diagnose` | Check the installation and print a report. Works without the service. Exits non-zero if a check fails. |
|
||||||
|
| `pdfs diagnostics` | What a running service is doing now: worker threads, queues, requests in flight, memory. For a service that has stopped answering. |
|
||||||
|
| `pdfs cache inspect [--deep]` | Database size, reclaimable space, row counts, cache use against the limit. `--deep` also runs SQLite's integrity check (slow). |
|
||||||
|
| `pdfs cache vacuum` | Compact the database. Needs free space for a second copy of it while it runs. |
|
||||||
|
| `pdfs cache clear` | Delete cached file content. Files kept available offline stay. |
|
||||||
|
|
||||||
|
[TROUBLESHOOTING.md](TROUBLESHOOTING.md) explains when to use each of these.
|
||||||
|
|
||||||
|
## Scripting with `--json`
|
||||||
|
|
||||||
|
The global `--json` flag makes query commands print JSON: `status`, `ls`, `pins`, `sync list`,
|
||||||
|
`devices list`, `locations`, `transfers`, `activity` and `cache inspect`. Commands that change
|
||||||
|
something keep their human-readable output; check their exit code.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pdfs --json status | jq -r '.mount.mountpoint'
|
||||||
|
pdfs --json sync list | jq -r '.items[] | select(.state != "idle") | .local_path'
|
||||||
|
pdfs --json cache inspect | jq '.db_reclaimable_bytes'
|
||||||
|
```
|
||||||
|
|
||||||
|
- The payload is unwrapped: a list is `{"items": [...]}`, never the service's internal message
|
||||||
|
name.
|
||||||
|
- An error from the service is printed as JSON with a machine-readable `kind` and still exits
|
||||||
|
non-zero, so `set -e` and `if pdfs …` behave as expected.
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
# Configuration
|
||||||
|
|
||||||
|
Most settings are in the app under **Preferences**. This page documents the configuration file
|
||||||
|
behind them, the settings that exist only there, the environment variables the programs read,
|
||||||
|
and where the client keeps its data.
|
||||||
|
|
||||||
|
- [Files and directories](#files-and-directories)
|
||||||
|
- [`config.json`](#configjson)
|
||||||
|
- [Environment variables](#environment-variables)
|
||||||
|
- [The systemd service](#the-systemd-service)
|
||||||
|
|
||||||
|
## Files and directories
|
||||||
|
|
||||||
|
| Path | Contents |
|
||||||
|
|---|---|
|
||||||
|
| `~/.config/proton-drive-linux/config.json` | Settings (this page) |
|
||||||
|
| `~/.local/state/proton-drive-linux/cache.db` | Metadata database: names, folder tree, sync state, upload queue, photo index |
|
||||||
|
| `~/.local/state/proton-drive-linux/sdk_cache.db` | Encrypted cache of Proton Drive metadata. Safe to delete while the service is stopped |
|
||||||
|
| `~/.local/state/proton-drive-linux/control.sock` | Socket the app, tray, prompt and CLI use to reach the service |
|
||||||
|
| `~/.cache/proton-drive-linux/content/` | File content cache, see below |
|
||||||
|
| `~/ProtonDrive` | The Proton Drive folder (default mountpoint) |
|
||||||
|
| System keyring | The session. Never written to disk by the client |
|
||||||
|
|
||||||
|
The directories honour `XDG_CONFIG_HOME`, `XDG_STATE_HOME` and `XDG_CACHE_HOME`. They must be real
|
||||||
|
directories owned by you with mode `0700`; the service refuses to start otherwise.
|
||||||
|
|
||||||
|
Inside `content/`:
|
||||||
|
|
||||||
|
| Directory | Contents | Safe to delete? |
|
||||||
|
|---|---|---|
|
||||||
|
| top level, `blocks/`, `thumbs/` | Cached file content and thumbnails | Yes — use `pdfs cache clear` |
|
||||||
|
| `scratch/` | Files open for writing right now | **No** |
|
||||||
|
| `staging/` | Closed files waiting to upload | **No** — may be the only copy |
|
||||||
|
| `recovery/` | Writes rescued after a crash, waiting to be queued | **No** — may be the only copy |
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> Never delete `staging/` or `recovery/` by hand. Check `pdfs sync queue` first. Content there has
|
||||||
|
> not reached Proton Drive and exists nowhere else.
|
||||||
|
|
||||||
|
Everything in these directories except the keyring is **decrypted**. See
|
||||||
|
[ARCHITECTURE.md §8](ARCHITECTURE.md#8-threat-model-what-this-client-writes-to-disk-in-plaintext).
|
||||||
|
|
||||||
|
## `config.json`
|
||||||
|
|
||||||
|
The file is created with defaults on first start. Every key is optional; a missing key takes its
|
||||||
|
default. Edit it while the app is closed, then restart the service
|
||||||
|
(`systemctl --user restart proton-drive.service`) for service settings to apply.
|
||||||
|
|
||||||
|
An unreadable file is never overwritten: the programs log an error and run with defaults until you
|
||||||
|
fix it.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mountpoint": "/home/you/ProtonDrive",
|
||||||
|
"cache_budget": 10737418240,
|
||||||
|
"upload_limit": null,
|
||||||
|
"download_limit": 5242880,
|
||||||
|
"ignore_patterns": ["node_modules/", "target/", "*.iso"],
|
||||||
|
"language": "de",
|
||||||
|
"proton_theme": true,
|
||||||
|
"online_map": false,
|
||||||
|
"tray_hidden": false,
|
||||||
|
"files_view": { "list": true, "sort": "modified", "descending": true, "folders_first": true },
|
||||||
|
"open_with": {
|
||||||
|
"terminal": ["alacritty", "-e"],
|
||||||
|
"rules": [{ "match": ["@text"], "command": ["nvim"], "terminal": true }]
|
||||||
|
},
|
||||||
|
"prompt": { "mode": "fzf", "menu_limit": 50 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reference
|
||||||
|
|
||||||
|
| Key | Type | Default | In the app | Meaning |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `mountpoint` | path | `~/ProtonDrive` | General → Location | Where My files is mounted |
|
||||||
|
| `cache_budget` | bytes | 5 GiB | Storage → Cache | Soft limit for cached content. `0` means unlimited. Available-offline files do not count against it |
|
||||||
|
| `upload_limit` | bytes/s | none | General → Network | Shared cap for all uploads. `0` or `null` means none |
|
||||||
|
| `download_limit` | bytes/s | none | General → Network | Shared cap for all downloads, reads from the mount included |
|
||||||
|
| `ignore_patterns` | list | see below | — | Global ignore rules for synced folders |
|
||||||
|
| `device_uid` | string | none | Computers → Continue This Backup Here… | The computer this machine continues. Unset means "match by hostname" |
|
||||||
|
| `conflict_sweep` | `off` / `report` / `enforce` | `report` | — | See [`conflict_sweep`](#conflict_sweep) |
|
||||||
|
| `language` | gettext code | system | General → Appearance | Language of the app, tray and prompt, such as `de` or `pt_BR` |
|
||||||
|
| `proton_theme` | bool | follow system | General → Appearance | Proton's colours instead of the system theme |
|
||||||
|
| `online_map` | bool | `false` | General → Appearance | Street map for Places from OpenFreeMap |
|
||||||
|
| `tray_hidden` | bool | `false` | General → Appearance | Hide the tray icon |
|
||||||
|
| `files_view` | object | grid, by name | My files view menu | `list`, `sort` (`name`, `size`, `modified`), `descending`, `folders_first` |
|
||||||
|
| `open_with` | object | `xdg-open` | — | See [`open_with`](#open_with) |
|
||||||
|
| `prompt` | object | built-in window | — | See [`prompt`](#prompt) |
|
||||||
|
|
||||||
|
`app_version` and `user_agent` identify the client to Proton. Leave them alone.
|
||||||
|
|
||||||
|
### `ignore_patterns`
|
||||||
|
|
||||||
|
Gitignore-style rules applied to every synced folder, in addition to each folder's own
|
||||||
|
`.pdfsignore`. When the key is absent, these defaults apply:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[".git/", ".hg/", ".svn/", "node_modules/", "target/", ".venv/", "__pycache__/",
|
||||||
|
"*~", "*.swp", "*.tmp", ".DS_Store", "Thumbs.db"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Set it to `[]` to turn the defaults off. See
|
||||||
|
[USER_GUIDE.md](USER_GUIDE.md#excluding-files-with-pdfsignore) for how ignoring behaves.
|
||||||
|
|
||||||
|
### `conflict_sweep`
|
||||||
|
|
||||||
|
A background task looks for `(sync-conflict …)` copies that are byte-identical to the file they
|
||||||
|
were copied from.
|
||||||
|
|
||||||
|
| Value | Behaviour |
|
||||||
|
|---|---|
|
||||||
|
| `off` | Do not run. |
|
||||||
|
| `report` | Log and show what would be removed; remove nothing. **Default.** |
|
||||||
|
| `enforce` | Move copies proven identical (same size and SHA-1) to the Trash. |
|
||||||
|
|
||||||
|
`PDFS_CONFLICT_SWEEP` overrides the setting for one run of the service.
|
||||||
|
|
||||||
|
### `open_with`
|
||||||
|
|
||||||
|
Decides how files picked in the search launcher or the app are opened. Rules are tried in order;
|
||||||
|
the first match wins.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"open_with": {
|
||||||
|
"terminal": ["alacritty", "-e"],
|
||||||
|
"default": ["xdg-open"],
|
||||||
|
"rules": [
|
||||||
|
{ "match": ["@text", "*.rs", "*.toml"], "command": ["nvim"], "terminal": true },
|
||||||
|
{ "match": ["*.png", "*.jpg"], "command": ["imv"] },
|
||||||
|
{ "match": ["@document"], "command": ["$EDITOR", "{}"], "terminal": true }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `match` takes file-name globs (`*.md`, `notes-*.txt`) or the classes `@dir`, `@text`,
|
||||||
|
`@document`, `@image`, `@media` and `@any`. Patterns are matched against the name in Drive, not
|
||||||
|
against the cached file, which is stored under a content hash.
|
||||||
|
- `command` is an argument list, not a shell line. `{}` is replaced by the path; without it the
|
||||||
|
path is appended. `$VAR` expands from the environment.
|
||||||
|
- `"terminal": true` runs the command inside `terminal`. When `terminal` is unset, the client uses
|
||||||
|
`$PDFS_TERMINAL`, then `$TERMINAL`, then the first known terminal on `PATH`, and adds that
|
||||||
|
terminal's "run this command" flag for you.
|
||||||
|
- `default` replaces `xdg-open` for files no rule matches.
|
||||||
|
|
||||||
|
### `prompt`
|
||||||
|
|
||||||
|
Settings for `pdfs-prompt`.
|
||||||
|
|
||||||
|
| Key | Default | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `mode` | `gtk` | Front end used without a flag: `gtk`, `dmenu` or `fzf` |
|
||||||
|
| `menu` | first installed of fuzzel, rofi, wofi, tofi, bemenu, dmenu | Launcher for `dmenu` mode, as an argument list. `{prompt}` is replaced by the prompt text; without it, the launcher's own prompt flag is added |
|
||||||
|
| `terminal` | first installed of foot, ghostty, kitty, alacritty, wezterm, xterm | Terminal for `fzf` mode. `{cmd}` is replaced by the command; without it, the command is appended |
|
||||||
|
| `menu_limit` | `50` | Results shown in `dmenu` and `fzf` mode |
|
||||||
|
| `menu_icons` | `true` | File-type icons in launchers that support them |
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"prompt": {
|
||||||
|
"mode": "dmenu",
|
||||||
|
"menu": ["fuzzel", "--dmenu", "--width", "60"],
|
||||||
|
"terminal": ["foot", "--app-id=pdfs-prompt", "--"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Command-line flags win over the file: `--gtk`, `--dmenu`, `--fzf` and `--menu`.
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
| Variable | Read by | Effect |
|
||||||
|
|---|---|---|
|
||||||
|
| `RUST_LOG` | all | Log filter, for example `RUST_LOG=debug` or `RUST_LOG=pdfs_fuse=debug,info`. Default `info` |
|
||||||
|
| `PDFS_CONFLICT_SWEEP` | service | Overrides `conflict_sweep` (`off`, `report`, `enforce`) |
|
||||||
|
| `PDFS_TERMINAL`, `TERMINAL` | app, prompt | Terminal for `open_with` rules with `"terminal": true` |
|
||||||
|
| `LANGUAGE`, `LC_ALL`, `LC_MESSAGES`, `LANG` | app, tray, prompt | Interface language when `language` is unset |
|
||||||
|
| `PDFS_LOCALEDIR` | app, tray, prompt | Load translations from another directory (development) |
|
||||||
|
| `PDFS_STAND_IN_WIDGETS` | app | `1` uses the libadwaita 1.5 fallback widgets on newer systems (development) |
|
||||||
|
|
||||||
|
The acceptance suite has its own `PDFS_ACCEPTANCE_*` variables, listed in [TESTING.md](TESTING.md).
|
||||||
|
|
||||||
|
## The systemd service
|
||||||
|
|
||||||
|
`proton-drive.service` is a user unit. It restarts the service whenever it exits, restarts it when
|
||||||
|
it stops answering for two minutes (`WatchdogSec=120`), and caps its memory at 6 GiB. To change
|
||||||
|
the unit without editing the packaged file, use a drop-in:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user edit proton-drive.service
|
||||||
|
```
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Service]
|
||||||
|
Environment=RUST_LOG=pdfs_fuse=debug,info
|
||||||
|
Environment=PDFS_CONFLICT_SWEEP=off
|
||||||
|
```
|
||||||
|
|
||||||
|
Logs go to the journal: `journalctl --user -u proton-drive.service -f`.
|
||||||
@@ -1,214 +1,164 @@
|
|||||||
# Development and Verification Status for 1.0
|
# Development guide
|
||||||
|
|
||||||
This document summarizes the work still pending after the 1.0.0 release branch and records the most important recently completed changes. [`BUGS.md`](BUGS.md) is the authoritative issue ledger; [`TESTING.md`](TESTING.md) defines the acceptance commands.
|
How to build, run, debug and release the client from source. Read
|
||||||
|
[CONTRIBUTING.md](../CONTRIBUTING.md) for the review rules and [ARCHITECTURE.md](ARCHITECTURE.md)
|
||||||
|
for how the pieces fit together.
|
||||||
|
|
||||||
---
|
- [Setup](#setup)
|
||||||
|
- [Repository layout](#repository-layout)
|
||||||
|
- [Running from source](#running-from-source)
|
||||||
|
- [Quality gates](#quality-gates)
|
||||||
|
- [Debugging](#debugging)
|
||||||
|
- [Common changes](#common-changes)
|
||||||
|
- [Releasing](#releasing)
|
||||||
|
|
||||||
## 1. Core Feature Roadmap (Horizon Tasks)
|
## Setup
|
||||||
Consolidated from the historical feature roadmap; current bug status lives in [`BUGS.md`](BUGS.md).
|
|
||||||
|
|
||||||
* **Local Cache & Metadata Encryption at Rest (Priority 5 / Horizon 1)**: Encrypt the SQLite database `cache.db` on disk using SQLCipher (`rusqlite` feature `bundled-sqlcipher`), and encrypt raw content cache blocks (`content/blocks/` and `content/scratch/`) using a fast symmetric scheme like AES-GCM or ChaCha20-Poly1305. The encryption key should be derived from the OS Keyring. (The SDK entity cache in `sdk_cache.db` is already encrypted at rest — see *Persistent SDK Entity Cache* below — but `cache.db` and the content cache are not.)
|
You need Rust 1.96 or newer and the native development packages listed under
|
||||||
* **Active Bandwidth Throttling & Traffic Shaping (Horizon 1)**: Add speed governors for daemon uploads and downloads. Support configuration variables `max_upload_rate_kbps` and `max_download_rate_kbps` in `config.json` that can be dynamically adjusted over the Unix control socket.
|
[Build dependencies](INSTALL.md#build-dependencies) for your distribution. Then:
|
||||||
* **Interactive & Policy-Based Conflict Resolution (Horizon 1)**: Implement configurable conflict resolution policies (`rename-local`, `rename-remote`, `prefer-local`, `prefer-remote`, `interactive`). Add a "Conflicts" tab to the GUI to notify the user, block sync, and launch external visual diff tools (like Meld or KDiff3).
|
|
||||||
* **Multi-Account / Multi-Profile Support (Horizon 3)**: Enable running multiple profiles concurrently. Support separate configuration namespaces (e.g. `profiles/<profile_name>/`), independent SQLite databases, distinct mount paths (e.g. `~/ProtonDrive/Personal` and `~/ProtonDrive/Work`), and multiple daemon processes.
|
|
||||||
* **Custom FUSE Mount Options (Horizon 1)**: Expose custom mounting parameters in configuration (e.g. `ro` for read-only, `direct_io` to bypass kernel caching, `allow_other`). Auto-generate systemd user mount units upon sync folder configuration.
|
|
||||||
* **Symbolic Link & Hard Link Virtualization (Horizon 2)**: Add symbolic link virtualization inside the metadata store (`NodeType::Symlink`) by intercepting `readlink(2)` and `symlink(2)`. Targets should sync remotely as small encrypted metadata placeholder files on Drive (`proton-vfs-symlink: <target>`).
|
|
||||||
* **Desktop File Manager Integration (Horizon 2)**: Develop shell extension plugins for popular Linux file managers (Nautilus Python plugin, Dolphin Service Menus, Thunar custom actions) to offer context-menu shortcuts ("Pin", "Unpin", "Copy Share Link", "View Version History") and file overlay sync badges.
|
|
||||||
* **Integrated Photo Gallery Enhancements (Horizon 2)**: Album *editing* — rename, cover photo, delete, and removing photos from an album — is still unported in the SDK (TS `updateAlbum` / `deleteAlbum` / `removePhotos`), as is adding a photo that lives on someone else's volume (TS `copyPhoto`). Album **creation** and adding own-volume photos landed with the Google Photos import. Still open: an auto-upload Pictures folder pipeline, and a GUI for creating albums by hand. Exif display, the date-based timeline scrubber and favorites are implemented.
|
|
||||||
* **Google Photos import follow-ups**: the importer uploads photos one at a time — a large export would finish sooner with a bounded concurrent upload pool. Trash folders are recognized by a hardcoded list of localized names ([`takeout.rs`](../crates/pdfs-core/src/takeout.rs) `TRASH_FOLDERS`); a locale not on it imports its trash as ordinary photos. Motion-photo `.MP`/`.MV` sidecar videos are ignored rather than uploaded as related photos. Live validation against a real export is pending.
|
|
||||||
* **Random-Access Media Streaming seek support (Horizon 2)**: Intercept out-of-order sparse reads (player seeks) in `pdfs-fuse` and prioritize downloading blocks around the seek offset, aborting/postponing sequential pre-fetches.
|
|
||||||
* **Dynamic Sync Dashboard & Queue Visualization (Horizon 2)**: GUI real-time transfer list (progress bars, speed, ETA), sync history feed, storage usage breakdown, and global pause/resume sync controls.
|
|
||||||
* **Interactive Terminal UI (TUI) Mode (Horizon 3)**: Implement `pdfs tui` using the `ratatui` crate to show transfer speed, sync status, active transfer queue with progress bars, and scrolling daemon logs.
|
|
||||||
* **Shutdown Safety & Write Queue Visibility (Horizon 1 / P4)**: Register systemd inhibitor locks in `pdfs-fuse` when there are outstanding staging writes to prevent shutdown or sleep, pop up warnings when exiting the GUI, and build a blocking `pdfs sync flush` command.
|
|
||||||
* **Block-Level Deduplication (Horizon 3)**: Transition to a Content-Addressable block cache where cached blocks are stored on disk by their SHA-256 hash. Map logical ranges in the database: `(node_uid, block_idx) -> block_hash` to avoid storing or downloading identical blocks.
|
|
||||||
* **Pre-emptive Sync Debouncing & File System Events (Horizon 3)**: Group filesystem write events on paths and delay sync queue insertion until a quiet period has elapsed (e.g. 5 seconds of inactivity) to prevent thrashing the Proton API.
|
|
||||||
|
|
||||||
---
|
```bash
|
||||||
|
git clone https://github.com/narrrl/proton-drive-linux.git
|
||||||
|
cd proton-drive-linux
|
||||||
|
cargo build --workspace
|
||||||
|
```
|
||||||
|
|
||||||
## 2. Performance, Correctness & Robustness Items
|
The GUI builds against libadwaita 1.5, the version in Ubuntu 24.04, and uses newer widgets at run
|
||||||
Consolidated from the performance and media-streaming audit notes.
|
time when the installed library has them (`crates/pdfs-gui/src/compat.rs`).
|
||||||
|
|
||||||
* **B6. Debounce the LRU access touch**: Keep last-touch times in memory and flush them in batches (debounce window of 30-60 seconds) to avoid performing a SQLite `UPDATE` to the `cache_entries` table on every cache hit.
|
## Repository layout
|
||||||
* **E12. Client in-flight cap shape**: Address the lock discrepancy between `MAX_CONCURRENT_BLOCK_DOWNLOADS = 10` (per-file) and `DEFAULT_MAX_INFLIGHT_BLOCKS = 12` (global client-wide). Either raise the global cap, lower the per-file window, or dynamically calculate the window to prevent single files from starving concurrent transfers.
|
|
||||||
* **Pipeline `enumerate_nodes_detail` (P2.8 / 8)**: Introduce pipelined node enumeration to speed up directory listings.
|
|
||||||
* **Local ancestor-chain walk (P3.4 / 4)**: Optimize recursive path lookups where full batching is impossible.
|
|
||||||
* **Name Search for Photos/Videos**: Add search by photo/video name (deferred from Phase 3 gallery implementation).
|
|
||||||
* **Sequential-Read Prefetch for Media Streaming**: Implement sequential read prefetch for media files to avoid stalls when buffering.
|
|
||||||
* **Minor sweep items (Phase F)**:
|
|
||||||
* **F1. `StreamRing.tags` Leak**: Clean up tag entries in `StreamRing.tags` when blocks are evicted. Currently, it only shrinks on `drop_node` (revision mismatch), leading to tag accumulation.
|
|
||||||
* **F2. Non-saturating bytes subtraction**: Use `saturating_sub` in `lib.rs:382` (`self.bytes -= dropped.len()`) to prevent release mode panics/wrapping if accounting drifts.
|
|
||||||
* **F3. Short block yields a short read / EOF**: Address the kernel EOF interpretation on short block reads. Either pad the block or fail loudly rather than silently serving short data.
|
|
||||||
* **F4. `stream_readahead` check-then-spawn race**: Prevent duplicate concurrent readahead tasks for the same block. Check-then-spawn is currently racey.
|
|
||||||
* **F5. `refresh_blocks` permit lock (SDK)**: Ensure URL refreshing (`refresh_blocks`) does not hold an in-flight block permit, preventing expired URLs from pinning client permits.
|
|
||||||
* **F7. Document temporary cache file safety**: Add explanatory comment to `cache.rs:362` explaining why `with_extension("tmp")` is safe there but not in `store_thumbnail`.
|
|
||||||
|
|
||||||
---
|
| Path | Contents |
|
||||||
|
|---|---|
|
||||||
|
| `crates/pdfs-core` | Shared library: authentication and keyring (`auth`), configuration (`config`), SQLite schema and queries (`db/`), content cache (`cache`), control protocol (`control`), search scoring, sync ignore rules, Google Takeout parsing, machine profile |
|
||||||
|
| `crates/pdfs-fuse` | The daemon: FUSE filesystem, read path (`reads`), write queue (`queue`, `drain`, `upload`), sync engine (`sync/`), remote events (`background`), push events for front ends (`events`), control handlers (`control`), photos, devices, supervisor |
|
||||||
|
| `crates/pdfs-cli` | The `pdfs` binary |
|
||||||
|
| `crates/pdfs-gui` | `pdfs-app`, `pdfs-tray` and `pdfs-prompt`. Pages in `src/app/pages/`, shared widgets in `src/app/widgets/`, CSS and icons in `resources/` |
|
||||||
|
| `po/` | Translation template, catalogs and the scripts that maintain them |
|
||||||
|
| `packaging/` | systemd unit, desktop files, icon, PKGBUILD, RPM spec, AUR recipes |
|
||||||
|
| `scripts/` | FUSE acceptance suite, map-data generators |
|
||||||
|
| `docs/` | This documentation |
|
||||||
|
|
||||||
## 3. Open & Unverified Bugs
|
The GUI talks to the daemon only through the control socket. Anything a front end needs that the
|
||||||
Derived from: [`BUGS.md`](BUGS.md)
|
daemon does not offer yet is a new request in `pdfs_core::control`, not a database read.
|
||||||
|
|
||||||
* **B2. Unattributed Trash Origin**: Investigate why deleted/moved files are occasionally sent to trash instead of vanishing on older rename operations.
|
## Running from source
|
||||||
* **B10. GLib Critical Warning on HUD Close**: Assertion failure `g_list_store_remove: assertion '!g_sequence_iter_is_end (it)' failed` when prompt window closes during active launch (`xdg-open`). Needs investigation under `G_DEBUG=fatal-criticals`.
|
|
||||||
* **B15. Empty-but-listed folders and duplicate UIDs**: Investigate folder-identity issues (e.g. duplicate remote folders like `Music` with different UIDs) and how folders obtain `listed = 1` in the database without matching child rows.
|
|
||||||
* **B8 (CAPTCHA sign-in bridge)**: Verify a real gated sign-in using Webkit CAPTCHA completion.
|
|
||||||
* **Draft Revision Upload loop**: Verify SDK-side deletion and retry logic when a conflicting draft revision exists under the same client UID.
|
|
||||||
* **Optimistic Size Loss on Restart**: Verify that pending uploads retain their optimistic sizes across a daemon restart.
|
|
||||||
* **B48 — stale listing after unlink**: Session tombstones and authoritative empty listings are implemented; run focused live verification against eventually consistent remote enumeration.
|
|
||||||
* **B49–B55 — data-safety fixes**: Conflict preservation, orphan/staging durability, transactional supersession, one-entry wipe protection, and incomplete-scan handling have regression coverage but still require the fault, power-loss, and live mode-matrix cases recorded in `BUGS.md`.
|
|
||||||
* **B56–B60 — fail-closed state and IPC fixes**: Future schemas, event cursor advancement, socket limits, private-directory validation, and atomic config saves are implemented. Malformed-schema, database-full, connection-flood, wrong-owner, and concurrent/faulted-save verification remains.
|
|
||||||
* **B61 — credential rotation persistence**: Surface and retry keyring persistence failures so a rotated single-use refresh token cannot be lost on restart.
|
|
||||||
* **B62–B65 — release assurance**: Clean-install package verification, complete version/protocol identity, recovery sign-off, advisory/license policy, SBOM, provenance, checksums, and signatures remain.
|
|
||||||
* **B66–B67 — lifecycle and recovery**: Add queue flush/shutdown protection and explicit device adoption during fresh-state restore.
|
|
||||||
* **B68/B78 — profile backup**: the destination is fixed and verified live — `profile.json` now lives in `.proton-drive-linux/` under the device root, since the API refuses file creation at the root itself. What remains from B68 is making backup *health* visible (today a failure is a `WARN` in the log and nothing in the UI) and re-running the replacement-machine restore end to end.
|
|
||||||
* **B79 — cross-mount permission classification**: fixed and verified live; the standing guard is the `regression B79` acceptance case. The second-account cases it neighbours (`regression B34`) still cannot run — see below.
|
|
||||||
|
|
||||||
---
|
The packaged service and a daemon run from source cannot share the same state directory. Stop the
|
||||||
|
service before you run your own build:
|
||||||
|
|
||||||
## 4. Testing, Verification & Disaster Recovery Drills
|
```bash
|
||||||
Derived from: [`TESTING.md`](TESTING.md) and [`RECOVERY.md`](RECOVERY.md)
|
systemctl --user stop proton-drive.service
|
||||||
|
RUST_LOG=pdfs_fuse=debug,info cargo run -p pdfs-cli -- daemon
|
||||||
|
```
|
||||||
|
|
||||||
* **IPC Memory and Performance Stress Tests (Phase 3)**: Verify memory allocations do not balloon when sending large `UploadPhoto` JSON payloads. Test control socket timeout behaviors under long-running block operations.
|
In another terminal:
|
||||||
* **Fault Injection and Resiliency Tests (Phase 4)**:
|
|
||||||
* **Network degradation**: Verify that interrupted uploads leave readable staging buffers and result in clean backoff retries.
|
|
||||||
* **Disk full (ENOSPC)**: Verify that write staging and gap-filling report correct errors to FUSE and preserve staged work.
|
|
||||||
* **Concurrent reader starvation**: Test that parallel sequential reads do not freeze directory lookups or metadata handlers.
|
|
||||||
* **Disaster Recovery Drill (P5 / Phase 5)**: Execute a complete manual disaster recovery test:
|
|
||||||
1. Register a device.
|
|
||||||
2. Sync a folder and upload data.
|
|
||||||
3. Wipe the local state directory and SQLite cache.
|
|
||||||
4. Restart the daemon.
|
|
||||||
5. Run `pdfs sync restore` and assert the recovered filesystem matches the source byte-for-byte.
|
|
||||||
* **Second-account sharing run (mount-architecture.md P7)**: The only outstanding verification for the mount/sharing work. One account covers `context_share_id`, the shared-with-me listing, sharing from device folders and the Locations page — all confirmed. A **second account** is required for the shared-tree rendering under a foreign volume, `accept_invitation`, `update_member_role`, the `regression B34` viewer cases (which skip cleanly today), and above all an **editor-role write into a foreign share**, the only thing that validates `membership_address_for`.
|
|
||||||
* **Managed live mode matrix**: Before release, run `scripts/fuse-acceptance.sh --managed-live <empty-a> <empty-b>` with `PDFS_ACCEPTANCE_ONLY` unset. It validates on-demand/on-demand, on-demand/mirror, mirror/on-demand, and mirror/mirror transitions.
|
|
||||||
* **Crash-consistency matrix**: Inject failure after scratch/sidecar sync, each staging rename, directory sync, pending-op insert/commit, and source deletion; prove that a complete discoverable copy and either the old or new queue record always survives.
|
|
||||||
|
|
||||||
---
|
```bash
|
||||||
|
cargo run -p pdfs-cli -- status
|
||||||
|
po/build.sh target/locale # compile translations once
|
||||||
|
PDFS_LOCALEDIR=target/locale cargo run -p pdfs-gui --bin pdfs-app
|
||||||
|
cargo run -p pdfs-gui --bin pdfs-tray
|
||||||
|
cargo run -p pdfs-gui --bin pdfs-prompt -- --fzf
|
||||||
|
```
|
||||||
|
|
||||||
## 5. Fixed & Resolved Bugs
|
To keep the systemd unit but point it at your build, see
|
||||||
Derived from: [`BUGS.md`](BUGS.md)
|
[INSTALL.md](INSTALL.md#using-a-binary-outside-usrbin).
|
||||||
|
|
||||||
### Completely Fixed & Verified
|
> [!CAUTION]
|
||||||
* **B1 — FUSE rename loses the file (data loss)**: In `rename`, the node's database row was deleted while only updating the in-memory state, causing the file to vanish on next listing sync. Fixed by correctly calling `st.invalidate_listing(newparent)` to refresh SQLite cache states.
|
> Develop against a test account. The acceptance suite and a buggy daemon can delete or rewrite
|
||||||
* **B4 — `invalidate_listing` silently skipped non-resident folders**: Folders not loaded in memory were being skipped during listing invalidation, leaving stale cache flags in the DB. Fixed by removing the residency check guard.
|
> files in Proton Drive.
|
||||||
* **B5 — `ls -l` costs a network round trip per file (thumbnail xattr probes)**: File listings triggered blocking network checks for thumbnails on unsupported files. Fixed by caching negative thumbnail results and restricting xattr advertisement to supported image/media files.
|
|
||||||
* **B6 — Daemon sets no file modes: `control.sock` is an unguarded authority**: The Unix control socket was created without restricting file permissions, exposing daemon controls to other local users. Fixed by applying proper creation permission masks.
|
|
||||||
* **B7 — renamed directory reads as missing until the entry TTL expires**: Kernel cache maps remained stale after a FUSE directory re-anchor. Fixed by proactively sending entry invalidation notifications to the kernel.
|
|
||||||
* **B9 — Enter did not open the selected result in the launcher**: Hooked up keyboard execution (Enter key) in `pdfs-prompt` search HUD to launch the selected item using `xdg-open`.
|
|
||||||
* **B11 — a file moved while its create is still queued reads as empty**: Reconciling a local rename on a file that had not finished uploading yet uploaded empty blocks. Fixed by carrying over and staging the correct logical file size.
|
|
||||||
* **B12 — cold enumeration is slow per entry, and goes superlinear past ~500**: S2K decryption and thumbnail resolution loops were serial. Fixed by parallelizing key derivations and caching metadata.
|
|
||||||
* **B13 — `rename` over an existing destination fails instead of replacing it**: Fixed POSIX rename target overwrite behavior to replace existing destinations.
|
|
||||||
* **B14 — provisional (ciphertext) sizes make rsync read short and abort the file**: Adjusted size reporting to align logical and ciphertext sizing, preventing premature EOF.
|
|
||||||
* **B45 — truncate over a queued rewrite**: Path-based and handle-based truncate now compose over the complete pending blob; shrink, growth, and sparse I/O are live-verified.
|
|
||||||
* **B46 — combined cross-directory move and rename**: The desired end state is durably queued and reconciles partially completed remote operations; replacement and combined cases are live-verified.
|
|
||||||
* **B47 — overlong/invalid names**: All name-taking callbacks share a validator for `NAME_MAX`, reserved components, and UTF-8; managed-live verification passed.
|
|
||||||
|
|
||||||
### Implemented in 1.0 (Further Fault Verification Pending)
|
> [!TIP]
|
||||||
|
> A daemon stopped in a debugger or deadlocked freezes every process that touches the mount,
|
||||||
|
> including your shell if its prompt reads the current directory. Keep your shell outside
|
||||||
|
> `~/ProtonDrive`, prefix commands that touch it with `timeout`, and unmount a dead mount with
|
||||||
|
> `fusermount3 -uz ~/ProtonDrive`.
|
||||||
|
|
||||||
* **Unified search and resident prompt**: `SearchV2` combines Drive and local lookup with shared relevance scoring and content/source filters. The prompt reuses its window and opens streamable media through FUSE.
|
## Quality gates
|
||||||
* **Durable write publication**: Scratch sidecars and staged blobs use synced temporary files, atomic renames, and directory synchronization; orphan rescue retains its source on failure.
|
|
||||||
* **Transactional pending operations**: Superseding a queued operation cannot delete the old durable record unless the replacement commits.
|
|
||||||
* **Safer mirror reconciliation**: Conflict-copy failure aborts publication, incomplete scans are non-destructive, and total-wipe protection includes one-entry baselines.
|
|
||||||
* **Bounded IPC**: Control requests are limited to 1 MiB and 10 seconds, with at most 64 active handlers.
|
|
||||||
* **Private local state**: Sensitive directories require correct ownership, real-directory identity, and `0700`; sockets use `0600`; config saves are atomic and malformed input is preserved.
|
|
||||||
* **Release gates**: Tag/workspace/package versions are checked and formatting, lint, tests, and offline FUSE acceptance run before packaging.
|
|
||||||
|
|
||||||
### Code Fixed / Implemented (Pending Verification)
|
CI (`.github/workflows/ci.yml`) runs these on every push to `main` and every pull request. Run them before you
|
||||||
* **B8 — no way to complete a CAPTCHA, so a gated sign-in is unrecoverable**: Implemented a Webkit-based bridge to prompt the user to resolve interactive CAPTCHAs during gated logins. (Needs verification on a real gate request).
|
push:
|
||||||
|
|
||||||
### Closed / Not a Bug
|
```bash
|
||||||
* **B3 — Activity log timestamps written in seconds, read as milliseconds**: Closed as investigator error (timestamps are consistent).
|
cargo fmt --all -- --check
|
||||||
|
cargo clippy --workspace --all-targets -- -D warnings
|
||||||
|
cargo test --workspace --locked
|
||||||
|
scripts/fuse-acceptance.sh --offline-only
|
||||||
|
```
|
||||||
|
|
||||||
---
|
For changes to the FUSE layer, the write queue or the sync engine, also run the live acceptance
|
||||||
|
cases described in [TESTING.md](TESTING.md).
|
||||||
|
|
||||||
## 6. Recently Implemented Features
|
For changes to GUI strings, check that every catalog is complete:
|
||||||
|
|
||||||
### File Version History (Implemented)
|
```bash
|
||||||
**Files**: [`revisions.rs`](../crates/pdfs-fuse/src/revisions.rs), [`control.rs`](../crates/pdfs-core/src/control.rs), [`versions_dialog.rs`](../crates/pdfs-gui/src/app/widgets/versions_dialog.rs)
|
for l in $(cat po/LINGUAS); do msgfmt --check --statistics -o /dev/null po/$l.po; done
|
||||||
|
```
|
||||||
|
|
||||||
Proton Drive keeps every revision a client committed; the daemon only ever addressed the active one, so a file overwritten by a sync pass could be recovered only from whatever the local `recovery/` directory happened to hold.
|
## Debugging
|
||||||
|
|
||||||
The control protocol gained `ListRevisions` / `RestoreRevision` / `DeleteRevision` / `SaveRevisionAs` (each with a `…ByUid` twin for nodes the primary mount cannot name), the CLI gained `pdfs versions list|restore|save|rm`, and the browser's details pane gained a **Versions** button opening a per-file dialog.
|
| Tool | Use |
|
||||||
|
|---|---|
|
||||||
|
| `RUST_LOG` | Log filter for every binary, for example `RUST_LOG=pdfs_fuse::sync=trace,info` |
|
||||||
|
| `pdfs diagnose` | Static checks: directories, FUSE, keyring, socket, database integrity. Works without the daemon |
|
||||||
|
| `pdfs diagnostics` | Live state of a running daemon: each worker's current job and its age, queue depth, control requests in flight, memory |
|
||||||
|
| `pdfs cache inspect --deep` | Database size, row counts and SQLite `integrity_check` |
|
||||||
|
| `pdfs --json …` | Raw protocol payloads for query commands |
|
||||||
|
| `GTK_DEBUG=interactive` | The GTK inspector for `pdfs-app` |
|
||||||
|
| `PDFS_STAND_IN_WIDGETS=1` | Force the libadwaita 1.5 fallback widgets on a newer system |
|
||||||
|
| `G_DEBUG=fatal-criticals` | Turn GLib criticals into crashes to get a backtrace |
|
||||||
|
|
||||||
Three properties that shape the code:
|
The databases are ordinary SQLite files. Open a copy, not the live file:
|
||||||
|
|
||||||
- **A restore is server-side and asynchronous.** No content crosses the wire and nothing enters the drain queue; the server answers 202 and swaps the active revision in the background, so the daemon evicts the file's cached blocks and open readers rather than describing the new state, and the UI wording promises the request, not the result.
|
```bash
|
||||||
- **The active revision cannot be deleted.** `Core::delete_revision_for_uid` checks that locally so the user gets a sentence rather than an API code, and the dialog gives the current row no delete button.
|
cp ~/.local/state/proton-drive-linux/cache.db /tmp/cache.db
|
||||||
- **Saving a version never overwrites.** `SaveRevisionAs` refuses an existing destination and removes a partial file if the download fails — a half-written export looks identical to a good one to every tool that opens it afterwards.
|
sqlite3 /tmp/cache.db '.tables'
|
||||||
|
```
|
||||||
|
|
||||||
### Photo Favorites (Implemented)
|
## Common changes
|
||||||
**Files**: [`photos.rs`](../crates/pdfs-fuse/src/photos.rs), [`photo_viewer.rs`](../crates/pdfs-gui/src/app/pages/photo_viewer.rs)
|
|
||||||
|
|
||||||
The gallery can mark and filter favorites, through the SDK's photo-tag API (`update_photos`). Schema **v20** adds `photos.favorite`; the flag is learned in the timeline enrichment pass that already resolves each photo's name and media type, and is kept across a refresh that could not resolve a photo — the same learned-and-kept rule as `media_type`. The lightbox carries a star toggle, the Photos header a favorites filter, and the CLI has `pdfs favorite <uid> [--remove]` plus `pdfs photos --favorites`.
|
**A new control request.** Add the variant to `Request` (and a `Response` if it returns data) in
|
||||||
|
`crates/pdfs-core/src/control.rs`, handle it in `crates/pdfs-fuse/src/control.rs`, and list the
|
||||||
|
`Topic`s it changes in the same file so subscribed front ends refresh. A daemon from the previous
|
||||||
|
release may still be running when a new front end starts, so treat an error reply to a new
|
||||||
|
request as "not supported".
|
||||||
|
|
||||||
Favouriting a photo that is not on this account's own photos volume (shared with us, or album-only) needs it re-encrypted for our timeline root, which the SDK does not implement; the daemon surfaces that as an error rather than silently doing nothing.
|
**A schema change.** Migrations are forward-only. Add a new step to
|
||||||
|
`crates/pdfs-core/src/db/migrations.rs` and bump `SCHEMA_VERSION`; never edit a shipped step. Add
|
||||||
|
a test in `crates/pdfs-core/src/db/tests.rs` that migrates from the previous version.
|
||||||
|
|
||||||
### Justified Gallery, Gallery Delete (Implemented)
|
**A GUI string.** Wrap it in the helpers from `crates/pdfs-gui/src/i18n.rs`, add any new source
|
||||||
**Files**: [`photos.rs`](../crates/pdfs-gui/src/app/pages/photos.rs), [`photo_viewer.rs`](../crates/pdfs-gui/src/app/pages/photo_viewer.rs), [`photos.rs`](../crates/pdfs-fuse/src/photos.rs), [`background.rs`](../crates/pdfs-fuse/src/background.rs)
|
file to `po/POTFILES.in`, run `po/update.sh`, and translate the new entries in every catalog. See
|
||||||
|
[TRANSLATING.md](TRANSLATING.md). The CLI and daemon messages stay in English.
|
||||||
|
|
||||||
The gallery lays each day out in justified rows (`justify_rows` / `plan_rows` / `fit_row`): photos are taken in capture order until their summed aspect ratio no longer fits the target row height, and the row is then scaled so it ends exactly on the content width. A tile is therefore its own photo's shape, and `ContentFit::Contain` has nothing left to crop. The last row of a day is left at the target height rather than stretched, because a day holding two photos is a short day, not a layout fault.
|
**A setting.** Add the field to `AppConfig` in `crates/pdfs-core/src/config.rs` with
|
||||||
|
`#[serde(default)]`, so older files still load, and document it in
|
||||||
|
[CONFIGURATION.md](CONFIGURATION.md).
|
||||||
|
|
||||||
- **Ratios that are not known yet.** `PhotoItem::ratio` is persisted, but a photo that has never been decoded has none. Those are laid out square and remembered in `assumed_ratios`; when a decode proves the real shape the day re-flows on the existing debounce timer, so a screenful of decodes costs one re-flow rather than one per photo. Ratios are clamped to 0.4–3.0 so a single panorama cannot decide what a row looks like.
|
## Releasing
|
||||||
- **Thumbnails survive recycling.** A reply for a tile that has already been re-bound still lands in the texture cache, which is an LRU of 1500 textures keyed by uid, and the rows just outside the realised range are prefetched in the scroll direction.
|
|
||||||
- **Delete is uid-addressed.** The photos volume is not in the FUSE mount, so the path-based `Request::Delete` cannot reach it. `Request::TrashNodes { uids }` trashes through the SDK and answers `Response::Trashed { trashed, failed }`; the daemon then drops those rows with `Db::photos_delete` (album membership included) so the gallery does not wait for a timeline refresh. The GUI removes the tiles optimistically with an Undo toast and puts back anything the server refused.
|
|
||||||
- **Remote deletions arrive on their own.** The Drive volume and the photos volume have separate event streams, so `run_photos_event_sync` polls the photos volume with its own cursor (`photos_event_cursor` in the state table). Trash and delete events remove rows; anything else only invalidates freshness, because there is no inode space to converge on this volume. Together with a 60 s `TIMELINE_TTL` and a `RefreshScope::Photos` that now awaits the refresh, a photo deleted on a phone leaves the grid in about ten seconds.
|
|
||||||
|
|
||||||
### RAW + JPEG Grouping (Implemented)
|
Releases are cut from `main` by pushing a `v*` tag. `.github/workflows/release.yml` then:
|
||||||
**Files**: [`photos.rs`](../crates/pdfs-core/src/db/photos.rs), [`migrations.rs`](../crates/pdfs-core/src/db/migrations.rs), [`photos.rs`](../crates/pdfs-fuse/src/photos.rs), [`photo_viewer.rs`](../crates/pdfs-gui/src/app/pages/photo_viewer.rs)
|
|
||||||
|
|
||||||
Schema **v30** adds `content_hash`, `main_uid` and an indexed `group_key` to `photos`. The first two come from the server's `PhotoProperties` and are filled by the enrichment pass in `refresh_timeline` that already resolves each photo's name, media type and favorite tag; `group_key` is computed in `photos_replace`, where the whole timeline is in hand.
|
1. checks that the tag, the workspace version in `Cargo.toml` and `pkgver` in
|
||||||
|
`packaging/PKGBUILD` agree;
|
||||||
|
2. runs the quality gates;
|
||||||
|
3. builds the release binaries and packages a `.tar.gz`, a `.deb` and a GPG-signed `.rpm`;
|
||||||
|
4. publishes them as a GitHub release;
|
||||||
|
5. calls `.github/workflows/aur.yml`, which updates and pushes the AUR recipes and commits them
|
||||||
|
back to `main`.
|
||||||
|
|
||||||
- **Precedence.** The server relation first (`main_photo_uid` and `related_photo_uids`, read from whichever end resolves — the two can land in different enrichment chunks); then same capture day, same case-insensitive name stem, and one member raw while the other is not; otherwise the photo is its own group. The union-find pass is over the timeline in memory, so it costs one pass per refresh rather than a query per photo.
|
To prepare a release:
|
||||||
- **Representative.** The non-raw member when there is one — a JPEG decodes in milliseconds and is what the person expects to see. `group_key` holds that member's uid, so "is this the tile" is `uid = group_key`, an index scan.
|
|
||||||
- **What is grouped and what is not.** `photos_page`, `photos_months` and `photos_counts` count groups, except on the Raw tab, which lists files. Album pages are never grouped: an album is a list someone made. The relation is learned-and-kept like `media_type`, so a refresh that could not resolve a node does not break a group up.
|
|
||||||
- **Deleting.** `Core::trash_photos` expands each uid to its group before trashing, because the grid shows the shot as one tile — leaving the RAW behind would put the photo back on the Raw tab and nowhere else. The GUI's confirmation names the file count.
|
|
||||||
- **The other files.** `Request::PhotoGroup { uid }` answers with the group's members as ordinary `PhotoItem`s, which is what the lightbox's switch steps through.
|
|
||||||
|
|
||||||
### Persistent SDK Entity Cache (Implemented)
|
```bash
|
||||||
**Files**: [`sdkcache.rs`](../crates/pdfs-core/src/sdkcache.rs), [`auth.rs`](../crates/pdfs-core/src/auth.rs), [`background.rs`](../crates/pdfs-fuse/src/background.rs)
|
# 1. Bump the version in Cargo.toml and packaging/PKGBUILD, then refresh the lock file.
|
||||||
|
cargo update --workspace
|
||||||
|
# 2. Move the [Unreleased] notes in docs/CHANGELOG.md under the new version.
|
||||||
|
# 3. Commit, tag and push.
|
||||||
|
git commit -am "chore(release): X.Y.Z"
|
||||||
|
git tag vX.Y.Z
|
||||||
|
git push origin main vX.Y.Z
|
||||||
|
```
|
||||||
|
|
||||||
The SDK's Drive entity cache (decrypted node metadata: name, size, parent, signing share) defaulted to memory, so every daemon restart re-fetched and re-decrypted the tree the previous run had already walked. It is now backed by SQLite.
|
Before a release that touches the filesystem or sync, run the managed live mode matrix from
|
||||||
|
[TESTING.md](TESTING.md). Open release-assurance items are listed in [ROADMAP.md](ROADMAP.md).
|
||||||
- **Its own file** (`sdk_cache.db` in the state directory), not `cache.db`: the daemon's `Db` is one `Mutex<Connection>` shared by every FUSE thread and the control socket, and this traffic is frequent, small and entirely reconstructible. A separate file also means no schema migration — the store can be deleted at any time.
|
|
||||||
- **Encrypted at rest** by wrapping it in the SDK's `EncryptedCacheRepository`, keyed by the mailbox password. A password change reads as a cold cache (the SDK treats an undecryptable entry as a miss and clears the store), not as an error.
|
|
||||||
- **Staleness** is closed by the event loop, which already replays from a persisted cursor and calls `invalidate_caches_for_event` for every event — including those raised while the daemon was down. The one case with no trail is a *seeded* cursor (first-ever mount, or a lost cursor), where the seed path now clears the store instead of trusting it. `auth::logout` removes the file.
|
|
||||||
|
|
||||||
Needed a small additive SDK change (`ProtonDriveClient::with_entity_repository`, 0.5.1): `with_entity_cache` is a constructor and so could not be combined with `with_key_salts`, which the daemon requires.
|
|
||||||
|
|
||||||
### Per-node Batch Outcomes (Implemented)
|
|
||||||
**File**: [`batch.rs`](../crates/pdfs-core/src/batch.rs)
|
|
||||||
|
|
||||||
SDK 0.5.0 made `trash_nodes` / `restore_nodes` / `delete_nodes` report one outcome per node instead of failing the whole call (mirroring upstream's streamed `NodeActionResult`). `pdfs_core::batch::into_unit` collapses the single-node calls back to a `Result`; `batch::split` handles a collected batch.
|
|
||||||
|
|
||||||
A restore is expanded before it is sent (`expand_restore`, `pdfs-fuse/src/lib.rs`): the persisted trash listing carries each row's `parent_uid` (schema 29), so a restore takes the connected piece of the trashed tree — every trashed descendant of what was asked for, and every trashed ancestor above it — and sends it shallowest wave first, because the server cannot put a node back under a parent that is still trashed. A uid the listing does not know about (it is materialised in chunks, and can be stale) is restored on its own rather than dropped.
|
|
||||||
|
|
||||||
The trash view's restore and permanent-delete use the SDK's **streaming** variants (`restore_nodes_streaming` / `delete_nodes_streaming`) and apply local state per node as each batch lands rather than after the last one. For a permanent delete — which is irreversible — that means a daemon interrupted mid-batch has forgotten exactly the nodes the server destroyed, no more and no fewer. Both report per-node failures and count only what actually succeeded; only a batch where nothing succeeded is an error.
|
|
||||||
|
|
||||||
|
|
||||||
### Open-for-Write Deferral for Mirror Sync (Implemented)
|
|
||||||
**File**: [`sync.rs`](../crates/pdfs-fuse/src/sync.rs)
|
|
||||||
|
|
||||||
The mirror folder sync engine now defers uploading any file that is currently held open for writing by another process, matching the guarantee the FUSE mount path already provides (where uploads are deferred until `close(fd)`).
|
|
||||||
|
|
||||||
**Problem**: Previously, the mirror sync path relied solely on a 2-second trailing-edge debounce (with a 30-second ceiling) to avoid uploading files mid-write. This was insufficient for slow continuous writers (e.g., database dumps, large exports, or editors that keep files open for extended periods), which could have their incomplete state uploaded as a real revision.
|
|
||||||
|
|
||||||
**Solution**: Before each reconcile pass, the sync engine scans `/proc/*/fd` once to build a set of canonical paths within the sync root that any process holds open for writing (`O_WRONLY` or `O_RDWR`). Files in this set are:
|
|
||||||
|
|
||||||
- **Kept in the local walk** — so they are not misclassified as deletions
|
|
||||||
- **Treated as unchanged** — so no upload is queued for them
|
|
||||||
- **Counted as `deferred`** — so the activity summary reports them (e.g., "3 uploaded, 1 deferred (open for write)")
|
|
||||||
- **Picked up on the next pass** — after the writer has closed the file
|
|
||||||
|
|
||||||
**How it works**:
|
|
||||||
1. `open_for_write_set(root)` reads `/proc/*/fd` → `readlink` for each fd → prefix-checks against the sync root → reads `/proc/<pid>/fdinfo/<n>` to check the `flags:` line's low two bits (O_ACCMODE)
|
|
||||||
2. `walk_local` sets `LocalItem.open_for_write = true` for matching files
|
|
||||||
3. Both `do_reconcile` and `push_pass` skip upload classification for flagged files
|
|
||||||
4. `Outcome.deferred` tracks the count for the activity summary
|
|
||||||
|
|
||||||
**Performance**: The `/proc` scan completes in low single-digit milliseconds on a typical desktop. Only fds whose `readlink` target falls under the sync root incur the `fdinfo` read.
|
|
||||||
|
|
||||||
**Tests added**: 8 unit tests covering `is_write_mode` parsing of various fdinfo flag combinations, and `Outcome` summary formatting with deferred counts.
|
|
||||||
|
|
||||||
| Sync Path | Open-for-write protection | Mechanism |
|
|
||||||
|---|---|---|
|
|
||||||
| FUSE mount | ✅ Perfect | Upload deferred until `close(fd)` |
|
|
||||||
| Mirror folders | ✅ Perfect | `/proc/*/fd` scan per reconcile pass |
|
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
# Installation
|
||||||
|
|
||||||
|
This page covers installing Proton Drive for Linux from a package or from source, the first
|
||||||
|
sign-in, running without a desktop, and removing the client again.
|
||||||
|
|
||||||
|
- [Requirements](#requirements)
|
||||||
|
- [Install a package](#install-a-package)
|
||||||
|
- [Build from source](#build-from-source)
|
||||||
|
- [First run](#first-run)
|
||||||
|
- [Headless and server use](#headless-and-server-use)
|
||||||
|
- [Updating](#updating)
|
||||||
|
- [Uninstalling](#uninstalling)
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
| | Minimum |
|
||||||
|
|---|---|
|
||||||
|
| Architecture | x86_64 (release packages); other architectures build from source |
|
||||||
|
| Kernel | FUSE 3 (`fuse3` package, `/dev/fuse` available to your user) |
|
||||||
|
| GTK / libadwaita | GTK 4.12, libadwaita 1.5 (1.8 or newer gets the newest widgets) |
|
||||||
|
| WebKitGTK | 6.0 — used only for the CAPTCHA page during sign-in |
|
||||||
|
| Secret Service | GNOME Keyring, KWallet, KeePassXC, or another provider of `org.freedesktop.secrets` |
|
||||||
|
| Rust (source builds) | 1.96 |
|
||||||
|
|
||||||
|
The session is stored only in the Secret Service. Without a running provider, sign-in succeeds but
|
||||||
|
cannot be saved, and the service cannot start unattended.
|
||||||
|
|
||||||
|
Optional runtime extras:
|
||||||
|
|
||||||
|
| Package | Adds |
|
||||||
|
|---|---|
|
||||||
|
| `exiftool` (`perl-image-exiftool`, `libimage-exiftool-perl`) | Thumbnails for camera RAW files |
|
||||||
|
| `ffmpeg` | Video thumbnails |
|
||||||
|
| `iso-codes` | Country names in your language on the Places map |
|
||||||
|
| `xdg-utils` | "Open Folder" in the tray, and the default file opener |
|
||||||
|
| `fzf` plus a terminal (foot, kitty, alacritty, …) | `pdfs-prompt --fzf`, the search-as-you-type launcher |
|
||||||
|
| AppIndicator extension (GNOME only) | The tray icon. KDE Plasma and most other desktops show it natively |
|
||||||
|
|
||||||
|
## Install a package
|
||||||
|
|
||||||
|
Every [release](https://github.com/narrrl/proton-drive-linux/releases/latest) ships a `.deb`, a
|
||||||
|
signed `.rpm` and a `.tar.gz` of the raw binaries. All packages install the same files:
|
||||||
|
|
||||||
|
| Path | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `/usr/bin/pdfs` | Command-line client and background service |
|
||||||
|
| `/usr/bin/pdfs-app` | Desktop app |
|
||||||
|
| `/usr/bin/pdfs-tray` | Tray icon |
|
||||||
|
| `/usr/bin/pdfs-prompt` | Search launcher |
|
||||||
|
| `/usr/lib/systemd/user/proton-drive.service` | User service that keeps the drive mounted |
|
||||||
|
| `/etc/xdg/autostart/io.narl.proton-drive-linux-tray.desktop` | Starts the tray at login |
|
||||||
|
| `/usr/share/locale/*/LC_MESSAGES/pdfs.mo` | Translations |
|
||||||
|
|
||||||
|
### Debian and Ubuntu (24.04 or newer)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install ./proton-drive-linux_*_amd64.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fedora (44 or newer)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo dnf install ./proton-drive-linux-*.x86_64.rpm
|
||||||
|
```
|
||||||
|
|
||||||
|
For a Fedora package built from your own checkout, see
|
||||||
|
[Build an RPM locally](#build-an-rpm-locally).
|
||||||
|
|
||||||
|
### Arch Linux (AUR)
|
||||||
|
|
||||||
|
Three packages cover the usual choices. They conflict with each other, so install one:
|
||||||
|
|
||||||
|
| Package | Contents |
|
||||||
|
|---|---|
|
||||||
|
| [`proton-drive-for-linux`](https://aur.archlinux.org/packages/proton-drive-for-linux) | Latest tagged release, built from source |
|
||||||
|
| [`proton-drive-for-linux-bin`](https://aur.archlinux.org/packages/proton-drive-for-linux-bin) | Latest tagged release, prebuilt binaries |
|
||||||
|
| [`proton-drive-for-linux-git`](https://aur.archlinux.org/packages/proton-drive-for-linux-git) | `main`, built from git |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
paru -S proton-drive-for-linux # or yay, or any other AUR helper
|
||||||
|
```
|
||||||
|
|
||||||
|
The recipes live in [`packaging/aur/`](../packaging/aur/README.md).
|
||||||
|
|
||||||
|
### Other distributions
|
||||||
|
|
||||||
|
Unpack the `.tar.gz` into a directory on your `PATH`, then install the service unit as shown in
|
||||||
|
[Using a binary outside `/usr/bin`](#using-a-binary-outside-usrbin).
|
||||||
|
|
||||||
|
## Build from source
|
||||||
|
|
||||||
|
### Build dependencies
|
||||||
|
|
||||||
|
**Debian / Ubuntu 24.04+**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install -y pkg-config gettext libfuse3-dev libgtk-4-dev libadwaita-1-dev \
|
||||||
|
libwebkitgtk-6.0-dev libsecret-1-dev libdbus-1-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fedora 44+**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo dnf install -y cargo rust gettext pkgconf-pkg-config fuse3-devel gtk4-devel \
|
||||||
|
libadwaita-devel webkitgtk6.0-devel libsecret-devel dbus-devel glib2-devel
|
||||||
|
```
|
||||||
|
|
||||||
|
**Arch Linux**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo pacman -S --needed rust gettext pkgconf fuse3 gtk4 libadwaita webkitgtk-6.0 libsecret dbus
|
||||||
|
```
|
||||||
|
|
||||||
|
Install Rust 1.96 or newer with [rustup](https://rustup.rs) if your distribution ships an older
|
||||||
|
toolchain.
|
||||||
|
|
||||||
|
### Compile
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/narrrl/proton-drive-linux.git
|
||||||
|
cd proton-drive-linux
|
||||||
|
cargo build --release --locked
|
||||||
|
po/build.sh target/locale # optional: compile the translations
|
||||||
|
```
|
||||||
|
|
||||||
|
The four binaries are in `target/release/`: `pdfs`, `pdfs-app`, `pdfs-tray` and `pdfs-prompt`.
|
||||||
|
|
||||||
|
### Build a package from the working tree
|
||||||
|
|
||||||
|
**Arch Linux:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd packaging && makepkg -fi
|
||||||
|
```
|
||||||
|
|
||||||
|
<a id="build-an-rpm-locally"></a>**Fedora:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo dnf install -y rpm-build
|
||||||
|
rpmbuild -bb packaging/proton-drive-linux.spec \
|
||||||
|
--define "git_dir $PWD" \
|
||||||
|
--define "_rpmdir $PWD/packaging/out" \
|
||||||
|
--define "_builddir $PWD/packaging/build" \
|
||||||
|
--define "_sourcedir $PWD" \
|
||||||
|
--define "_specdir $PWD/packaging" \
|
||||||
|
--define "_srcrpmdir $PWD/packaging/out"
|
||||||
|
sudo dnf install packaging/out/x86_64/proton-drive-linux-*.rpm
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using a binary outside `/usr/bin`
|
||||||
|
|
||||||
|
The service unit starts `/usr/bin/pdfs daemon`. If you installed the binaries somewhere else, for
|
||||||
|
example `~/.local/bin`, install the unit for your user and point it at your binary:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.config/systemd/user
|
||||||
|
cp packaging/proton-drive.service ~/.config/systemd/user/
|
||||||
|
sed -i "s|^ExecStart=.*|ExecStart=$HOME/.local/bin/pdfs daemon|" \
|
||||||
|
~/.config/systemd/user/proton-drive.service
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
```
|
||||||
|
|
||||||
|
To start the tray at login, copy `packaging/io.narl.proton-drive-linux-tray.desktop` to
|
||||||
|
`~/.config/autostart/`. For the app launcher entry, copy `packaging/io.narl.proton-drive-linux.desktop`
|
||||||
|
to `~/.local/share/applications/` and the icon `packaging/io.narl.proton-drive-linux.svg` to
|
||||||
|
`~/.local/share/icons/hicolor/scalable/apps/`.
|
||||||
|
|
||||||
|
## First run
|
||||||
|
|
||||||
|
### With the desktop app
|
||||||
|
|
||||||
|
1. Open **Proton Drive** from your application menu, or run `pdfs-app`.
|
||||||
|
2. Sign in with your Proton email and password, and your two-factor code if the account has one.
|
||||||
|
If Proton asks for a CAPTCHA (common on a VPN or a new network), the app shows the challenge in
|
||||||
|
a window and continues after you solve it.
|
||||||
|
3. The app enables and starts `proton-drive.service`. Your Drive appears at `~/ProtonDrive`.
|
||||||
|
|
||||||
|
The service is tied to your graphical session. It starts after you log in and stops when you log
|
||||||
|
out. Change the folder location under **Preferences → General → Location**.
|
||||||
|
|
||||||
|
### With the command line
|
||||||
|
|
||||||
|
`pdfs login` stores the session but does not enable the service, so a setup that runs the daemon
|
||||||
|
another way stays untouched. Enable the service yourself:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pdfs login # email, password, 2FA code
|
||||||
|
systemctl --user enable --now proton-drive.service
|
||||||
|
pdfs status # account, mount and queue state
|
||||||
|
```
|
||||||
|
|
||||||
|
The CLI cannot show a CAPTCHA. If sign-in reports one, sign in once with `pdfs-app` instead; the
|
||||||
|
session it stores is used by the CLI and the service as well.
|
||||||
|
|
||||||
|
## Headless and server use
|
||||||
|
|
||||||
|
The service unit is bound to `graphical-session.target`. On a machine without a desktop session,
|
||||||
|
run the daemon directly or from your own unit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pdfs daemon # waits for a stored session, mounts, stays mounted
|
||||||
|
pdfs mount /srv/proton # mounts once at a given path, blocks until unmounted
|
||||||
|
```
|
||||||
|
|
||||||
|
Both still need a Secret Service provider. On a server, run
|
||||||
|
`gnome-keyring-daemon --unlock` or KeePassXC's Secret Service integration inside the user's D-Bus
|
||||||
|
session.
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
Install the new package over the old one. The service picks up the new binary the next time it
|
||||||
|
starts; to switch at once:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user restart proton-drive.service
|
||||||
|
```
|
||||||
|
|
||||||
|
Database migrations run automatically and only move forward. A database written by a newer
|
||||||
|
release refuses to open in an older one, so downgrading means clearing the local database (see
|
||||||
|
[Troubleshooting](TROUBLESHOOTING.md#start-over-with-a-clean-local-state)).
|
||||||
|
|
||||||
|
## Uninstalling
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Check that nothing is waiting to upload before you remove local state. `pdfs sync queue` must be
|
||||||
|
> empty. Queued writes exist only on this computer until they reach Proton Drive.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pdfs sync queue # must list nothing
|
||||||
|
systemctl --user disable --now proton-drive.service
|
||||||
|
pdfs logout # removes the session from the keyring
|
||||||
|
```
|
||||||
|
|
||||||
|
Then remove the package (`sudo apt remove proton-drive-linux`, `sudo dnf remove
|
||||||
|
proton-drive-linux`, or `sudo pacman -R proton-drive-for-linux`), and, if you want no trace left,
|
||||||
|
the local data:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm -rf ~/.config/proton-drive-linux ~/.local/state/proton-drive-linux ~/.cache/proton-drive-linux
|
||||||
|
```
|
||||||
|
|
||||||
|
Sign out of the device in your Proton account settings as well, and remove the computer from
|
||||||
|
**Computers** in the Proton Drive web app if you no longer want its backups.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Documentation
|
||||||
|
|
||||||
|
## For users
|
||||||
|
|
||||||
|
| Page | What it covers |
|
||||||
|
|---|---|
|
||||||
|
| [Installation](INSTALL.md) | Packages, building from source, first sign-in, updating and uninstalling |
|
||||||
|
| [User guide](USER_GUIDE.md) | My files, available offline, synced folders, conflicts, Photos, sharing, Trash and versions, Computers, the search launcher, the tray |
|
||||||
|
| [Command-line reference](CLI.md) | Every `pdfs` command, grouped by task, and scripting with `--json` |
|
||||||
|
| [Configuration](CONFIGURATION.md) | `config.json`, environment variables, file locations, the systemd service |
|
||||||
|
| [Troubleshooting](TROUBLESHOOTING.md) | Diagnosing problems, common symptoms, starting over, reporting a bug |
|
||||||
|
| [Recovering on a new computer](RECOVERY.md) | What survives a lost machine and how to restore your synced folders |
|
||||||
|
| [Changelog](CHANGELOG.md) | What changed in each release |
|
||||||
|
|
||||||
|
## For contributors
|
||||||
|
|
||||||
|
| Page | What it covers |
|
||||||
|
|---|---|
|
||||||
|
| [Development guide](DEVELOPMENT.md) | Building, running and debugging from source; releasing |
|
||||||
|
| [Architecture](ARCHITECTURE.md) | Crates, read and write paths, sync engine, control protocol, threads, threat model |
|
||||||
|
| [Testing](TESTING.md) | Unit tests and the FUSE acceptance suite |
|
||||||
|
| [Translating](TRANSLATING.md) | Adding and updating translations |
|
||||||
|
| [Roadmap](ROADMAP.md) | Planned work and verification still owed |
|
||||||
|
| [Bug ledger](BUGS.md) | Every tracked defect with its analysis and status |
|
||||||
|
| [UI/UX plan](UI_UX_PLAN.md) | Design audit and notes behind the 2.x desktop app |
|
||||||
|
|
||||||
|
Also see [CONTRIBUTING.md](../CONTRIBUTING.md) and [SECURITY.md](../SECURITY.md) in the repository
|
||||||
|
root.
|
||||||
@@ -1,262 +1,168 @@
|
|||||||
# Recovery: Getting a Replacement Machine Back onto Your Device
|
# Recovering on a new computer
|
||||||
|
|
||||||
You lose the laptop. You install `pdfs` on a new one. What does it take to get the
|
Your laptop is lost, stolen or reinstalled. This page explains what survives, and how to get
|
||||||
synced folders back?
|
your synced folders back on the replacement machine.
|
||||||
|
|
||||||
This document is two things: **§2 is the runbook that works with the client as it
|
- [1. What survives](#1-what-survives)
|
||||||
stands today**, gotchas included, and **§3 is the plan to reduce that runbook to
|
- [2. Restore your synced folders](#2-restore-your-synced-folders)
|
||||||
one command.** §1 is the part neither can fix — what actually died with the
|
- [3. How the restore works](#3-how-the-restore-works)
|
||||||
machine.
|
- [4. Known gaps](#4-known-gaps)
|
||||||
|
|
||||||
---
|
## 1. What survives
|
||||||
|
|
||||||
## 1. What survives, and what does not
|
### On Proton Drive
|
||||||
|
|
||||||
### Survives (it is on Drive, or derivable)
|
| What | Where |
|
||||||
|
|
||||||
| Thing | Where it lives |
|
|
||||||
|---|---|
|
|---|---|
|
||||||
| Every file that finished uploading | Proton Drive |
|
| Every file that finished uploading | Proton Drive |
|
||||||
| The Device registration itself | Proton Drive (`enumerate_devices`) |
|
| The computer's registration and its backup | **Computers** in the app and the web |
|
||||||
| The device's folder tree — one folder per synced folder | Proton Drive, under the device root |
|
| One folder per synced folder, under that computer | Proton Drive |
|
||||||
| Your account, keys, sharing | Proton |
|
| The machine profile: which folders were synced, to which local path, in which mode; pins; ignore patterns | `.proton-drive-linux/profile.json` under the computer's backup, end-to-end encrypted like any other file |
|
||||||
|
| Your account, keys and shares | Proton |
|
||||||
|
|
||||||
### Does not survive
|
The service rewrites the profile a short while after you add, remove or switch a synced folder,
|
||||||
|
or change a pin.
|
||||||
|
|
||||||
| Thing | Why | Recoverable? |
|
### Lost with the machine
|
||||||
|
|
||||||
|
| What | Why | Recoverable? |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Undrained `staging/` + `recovery/` blobs | Writes the kernel accepted but that never reached Drive. They existed only on that disk. | **No.** |
|
| Changes still in `staging/` or `recovery/` | Writes that were accepted locally but never reached Proton Drive | **No** |
|
||||||
| Local-only files in a mirror folder that had not uploaded yet | Same. | **No.** |
|
| New files in a synced folder that had not uploaded yet | Same | **No** |
|
||||||
| `sync_folder` rows — which local path maps to which remote folder, and in which mode | Local SQLite (`cache.db`) | Rebuildable by hand (§2), not automatically (§3) |
|
| `config.json`: mountpoint, cache limit, bandwidth limits, appearance | Local config directory | Set again in **Preferences** |
|
||||||
| `sync_entry` baseline | Local SQLite | Not needed. An empty baseline reconciles correctly — see §2.4. |
|
| The local database and content cache | Local disk | Not needed; rebuilt from Proton Drive |
|
||||||
| Pins | Local SQLite | Rebuildable by hand |
|
|
||||||
| `config.json` — mountpoint, cache budget | Local config dir | Rebuildable by hand |
|
|
||||||
| `cache.db` node/FTS/photo caches | Local SQLite | Pure cache; rebuilds itself |
|
|
||||||
|
|
||||||
The first two rows are the only genuinely unrecoverable ones, and today nothing
|
Only the first two rows are real losses. Before you retire a working machine, check that
|
||||||
warns you they are non-empty before you walk away from a machine. That is tracked
|
`pdfs sync queue` prints "Nothing queued" (or that **Sync → Overview** shows nothing waiting).
|
||||||
separately from this document as a pending-writes visibility gap.
|
|
||||||
|
|
||||||
### Before anything else: revoke
|
### Before anything else: revoke the old session
|
||||||
|
|
||||||
The lost machine holds a decrypted content cache, a plaintext index of every node
|
The lost machine holds a decrypted content cache, a plain-text index of your file names and a
|
||||||
name in your Drive (`cache.db`), and a live session in its keyring. Whether that
|
live session in its keyring. Whether its disk was encrypted decides how much of that is exposed,
|
||||||
matters depends on whether its disk was encrypted — but the session does not.
|
but the session is valid either way.
|
||||||
|
|
||||||
**Revoke the session from your Proton account settings before restoring.** Nothing
|
**Sign the lost machine out from your Proton account's security settings before you restore.**
|
||||||
in this client can do that for you; the daemon on the lost machine holds
|
The client cannot do that for you.
|
||||||
credentials it will happily keep refreshing.
|
|
||||||
|
|
||||||
If you have also lost your password, Proton's recovery phrase is what determines
|
If you have also lost your password, account recovery is Proton's matter, not this client's.
|
||||||
whether the data is reachable at all. That is upstream of this client entirely: if
|
Proton Drive is end-to-end encrypted: without the password or a recovery method, the data cannot
|
||||||
zero-knowledge encryption worked as advertised, nobody at Proton can help.
|
be decrypted.
|
||||||
|
|
||||||
---
|
## 2. Restore your synced folders
|
||||||
|
|
||||||
## 2. The runbook that works today
|
### Step 1: Install and sign in
|
||||||
|
|
||||||
### 2.1 Install, log in
|
Install the client as described in [INSTALL.md](INSTALL.md) and sign in with `pdfs-app`. Signing
|
||||||
|
in from the app also starts the service.
|
||||||
|
|
||||||
|
### Step 2: Continue the old computer's backup
|
||||||
|
|
||||||
|
Tell this machine which computer it replaces, so new changes go into the existing backup instead
|
||||||
|
of a second, empty one.
|
||||||
|
|
||||||
|
- **In the app:** open **Computers**, open the menu of the old computer and choose
|
||||||
|
**Continue This Backup Here…**.
|
||||||
|
- **From a terminal:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pdfs login # stores the session in the keyring
|
pdfs devices list # find the old computer's UID
|
||||||
systemctl --user start proton-drive # or let autostart do it
|
pdfs devices adopt UID
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2.2 Re-adopt the device — check this before anything else
|
Adoption takes effect at once and is stored in `config.json` as `device_uid`. It survives a
|
||||||
|
change of hostname. `pdfs devices adopt --clear` returns to the default, which is to match a
|
||||||
|
computer whose name equals this machine's hostname.
|
||||||
|
|
||||||
`Core::ensure_device` recovers an existing device by **matching a Linux device
|
> [!NOTE]
|
||||||
whose name equals this machine's hostname**, read from
|
> Without adoption, the client only finds the old computer if this machine has the same
|
||||||
`/proc/sys/kernel/hostname`. So:
|
> hostname. Otherwise it registers a new computer, and nothing points at the old backup.
|
||||||
|
|
||||||
```bash
|
### Step 3: Restore the folders
|
||||||
hostnamectl hostname # what this machine will register as
|
|
||||||
pdfs devices list # what the account already has
|
- **In the app:** open **Computers**, open the menu of **This computer** and choose
|
||||||
|
**Restore Folders…**. Confirm or change the local path of each folder.
|
||||||
|
- **From a terminal:**
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ pdfs sync restore
|
||||||
|
Documents [mirror] -> /home/you/Documents (Enter to accept, 'n' to skip, or a path)
|
||||||
|
Projects [ondemand] -> /home/you/Projects (Enter to accept, 'n' to skip, or a path)
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Hostnames match** → the new machine silently re-adopts the old device and its
|
Press Enter to accept a path, type `n` to skip the folder, or type another path. `--yes`
|
||||||
root folder. This is the happy path, and it is why the rest of the runbook works.
|
accepts every proposal without asking.
|
||||||
- **Hostnames differ** → `pdfs` registers a **second device**. Nothing errors.
|
|
||||||
Your old device and all its folders are still there, but this machine is not
|
|
||||||
attached to them, and every step below will quietly create empty folders under
|
|
||||||
the new device instead.
|
|
||||||
|
|
||||||
> **The single most important step in this document:** if the hostnames differ,
|
For each folder you accept, the service creates the local directory, attaches it to the folder in
|
||||||
> set the new machine's hostname to match the old device's name *before* adding
|
the backup, restores its mode (**Mirror** or **Online only**) and downloads it. Follow the
|
||||||
> any sync folders.
|
progress on **Sync → Overview** or with `pdfs sync list`.
|
||||||
> ```bash
|
|
||||||
> sudo hostnamectl set-hostname OLD-DEVICE-NAME
|
|
||||||
> ```
|
|
||||||
> There is no supported way to adopt a device by uid yet — see §3.1.
|
|
||||||
|
|
||||||
### 2.3 Find out which folders the device had
|
> [!TIP]
|
||||||
|
> Restore into empty or missing directories. If a directory already holds files, for example
|
||||||
|
> from another backup, the client cannot tell which side is newer. It keeps both: your local file
|
||||||
|
> is renamed to a `(sync-conflict …)` copy and the version from Proton Drive is downloaded. See
|
||||||
|
> [Conflicts](USER_GUIDE.md#conflicts).
|
||||||
|
|
||||||
```bash
|
### Step 4: Restore the rest by hand
|
||||||
pdfs devices list # confirm the device and note its name
|
|
||||||
```
|
|
||||||
|
|
||||||
The device's synced folders are the folders directly under its root in Drive.
|
- **Available offline files.** The profile records them, but the restore does not apply them yet.
|
||||||
Browse them in the GUI's Computers page, or in the Proton Drive web app under
|
Mark them again in the app, or with `pdfs pin PATH`.
|
||||||
that device. **Write down their exact names** — you need them in the next step.
|
- **Preferences.** Set the cache limit, bandwidth limits, mountpoint and appearance again.
|
||||||
|
|
||||||
### 2.4 Re-attach each folder
|
### Step 5: Clean up
|
||||||
|
|
||||||
For each folder name you noted, create the local directory and add it:
|
If this machine registered a second computer before you adopted the old one, remove it:
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p ~/Documents
|
|
||||||
pdfs sync add ~/Documents
|
|
||||||
```
|
|
||||||
|
|
||||||
`add_sync_folder` looks for a folder under the device root named after the local
|
|
||||||
directory's **basename**, and reuses it if found rather than creating a second
|
|
||||||
one. So `~/Documents` re-attaches to the existing `Documents`. With an empty
|
|
||||||
baseline and an empty local directory, the first reconcile classifies every remote
|
|
||||||
path as `(None, Some(remote))` with no baseline → **download**, and the folder
|
|
||||||
refills from Drive.
|
|
||||||
|
|
||||||
Three things to be careful about:
|
|
||||||
|
|
||||||
- **The basename must match exactly.** Restoring to `~/docs` when the device
|
|
||||||
folder is `Documents` does not re-attach — it creates a *new*, empty `docs`
|
|
||||||
folder in Drive and syncs nothing. The failure is silent; you just see an empty
|
|
||||||
directory. Check with `pdfs sync list` and the Computers page.
|
|
||||||
- **Add the folder empty, or accept conflict copies.** If the local directory
|
|
||||||
already has files (restored from a backup, say), there is no baseline to say
|
|
||||||
which side is newer, so both sides read as changed and the reconcile takes the
|
|
||||||
conflict arm: your local file is renamed to a `(sync-conflict <ts>)` copy and
|
|
||||||
the remote version is downloaded. Nothing is lost, but it is noisy. Restoring
|
|
||||||
into an empty directory avoids it.
|
|
||||||
- **A wholly empty local tree is now safe.** Before the audit fixes, adding a
|
|
||||||
folder whose local side was empty against a populated remote could, on a later
|
|
||||||
pass, be read as "the user deleted everything" and trash the lot. `sync.rs`'s
|
|
||||||
`guard_local_wipe` now refuses any pass where every baseline path has vanished
|
|
||||||
locally, which is exactly the restore shape.
|
|
||||||
|
|
||||||
### 2.5 Restore modes, pins, and settings by hand
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pdfs sync list # note the ids
|
|
||||||
pdfs sync mode <id> ondemand # for folders that were on-demand
|
|
||||||
```
|
|
||||||
|
|
||||||
Nothing records which folders were `ondemand` before, so this is from memory. Pins
|
|
||||||
and the cache budget/mountpoint likewise: set them again from the Settings page.
|
|
||||||
|
|
||||||
### 2.6 Clean up
|
|
||||||
|
|
||||||
If a duplicate device got created before you noticed the hostname mismatch:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pdfs devices list
|
pdfs devices list
|
||||||
pdfs devices rm <uid-of-the-stray-device>
|
pdfs devices rm UID
|
||||||
```
|
```
|
||||||
|
|
||||||
Check what is under it in the web app first. Deleting a device you actually
|
> [!WARNING]
|
||||||
attached folders to will take its folders with it.
|
> Removing a computer deletes its backup from Proton Drive. Check what is under it in
|
||||||
|
> **Computers → Browse Files** first.
|
||||||
|
|
||||||
---
|
### Restoring another computer's folders
|
||||||
|
|
||||||
## 3. The plan: make this one command
|
To bring folders from a computer that still exists, for example a desktop whose documents you want
|
||||||
|
on a laptop, use **Restore to This Computer…** on that computer's row, or:
|
||||||
|
|
||||||
The pieces all exist — device enumeration, folder-name reuse, an empty-baseline
|
```bash
|
||||||
reconcile that downloads correctly. What is missing is that the restore is
|
pdfs sync restore --device UID
|
||||||
**implicit, name-based, and undiscoverable**. Each phase below removes one of
|
```
|
||||||
those properties. They are independent and land in this order by value.
|
|
||||||
|
|
||||||
### P1 — Adopt a device explicitly, by uid
|
The folders stay part of the other computer's backup and sync in both directions. This machine
|
||||||
|
does not take over that computer's backup.
|
||||||
|
|
||||||
**Problem:** device identity is inferred from the hostname. A new laptop with a
|
## 3. How the restore works
|
||||||
new name silently forks a second device, and the failure looks like "my files
|
|
||||||
didn't come back" rather than an error.
|
|
||||||
|
|
||||||
- Add `Request::AdoptDevice { uid }`: validate the uid against
|
This section is for the curious and for anyone debugging a restore.
|
||||||
`enumerate_devices`, write the `StoredDevice` row, and refuse if any
|
|
||||||
`sync_folder` rows already reference a different device.
|
|
||||||
- `pdfs devices adopt <uid>`, and an "Adopt this device" action on rows in the
|
|
||||||
GUI's Computers page that are not the current machine.
|
|
||||||
- Make the fork *visible* rather than silent: when `ensure_device` is about to
|
|
||||||
create a device and the account already has other Linux devices, log a warning
|
|
||||||
naming them, and surface it in `pdfs status`.
|
|
||||||
|
|
||||||
Keep hostname matching as the default. It is right for the common case; it just
|
- **Which computer the machine is.** At start, the service uses `device_uid` from `config.json`
|
||||||
must not be the *only* way.
|
when it is set. When it is not, it looks for a Linux computer in the account whose name equals
|
||||||
|
the hostname, and registers a new one when there is none. An adopted UID that no longer exists
|
||||||
|
in the account is reported in the log instead of being replaced silently.
|
||||||
|
- **What can be restored.** `pdfs sync restore` lists the folders directly under the computer's
|
||||||
|
backup, except `.proton-drive-linux`. Folders already synced on this machine are skipped.
|
||||||
|
- **Proposed paths.** When the profile names a local path whose parent directory exists on this
|
||||||
|
machine, that path is proposed. Otherwise the proposal is `~/<folder name>`. The service never
|
||||||
|
writes to a path you have not confirmed.
|
||||||
|
- **Attaching by UID.** The restore binds each local path to the remote folder's UID, not to its
|
||||||
|
name. Restoring `Documents` into `~/docs` works.
|
||||||
|
- **Downloading.** A restored folder starts with an empty sync baseline. Against an empty local
|
||||||
|
directory, every remote file counts as new and is downloaded. Files present on both sides with
|
||||||
|
no baseline become conflict copies, as described above.
|
||||||
|
- **The wipe guard.** A sync pass in which every previously synced file has vanished locally is
|
||||||
|
refused instead of being mirrored to Proton Drive as a mass deletion. A folder that was emptied
|
||||||
|
by accident therefore does not empty the backup.
|
||||||
|
- **The profile format.** `profile.json` carries a `version` field. A client refuses a profile
|
||||||
|
written by a newer, incompatible client instead of applying part of it.
|
||||||
|
|
||||||
### P2 — Discover and re-attach the device's folders
|
## 4. Known gaps
|
||||||
|
|
||||||
**Problem:** §2.3 and §2.4 are "remember what you had, then retype it exactly."
|
| Gap | Effect |
|
||||||
|
|
||||||
- Add `Request::ListDeviceFolders { device_uid }` — enumerate the device root's
|
|
||||||
child folders and report name, node uid, child count, and total size. This is
|
|
||||||
the "what did this machine sync?" answer that today requires the web app.
|
|
||||||
- Add `Request::AttachSyncFolder { remote_uid, local_path, mode }` — the
|
|
||||||
re-attach counterpart to `AddSyncFolder`. It binds a local path to a **specific
|
|
||||||
remote uid** rather than resolving by basename, which removes the silent
|
|
||||||
`~/docs` vs `Documents` failure entirely.
|
|
||||||
- CLI: `pdfs sync restore [--device <uid>] [--into <dir>]` — list the device's
|
|
||||||
folders, propose `<dir>/<name>` for each (default `$HOME`), and attach the ones
|
|
||||||
the user confirms.
|
|
||||||
- GUI: a "Restore folders from this device" flow on the Computers page, with a
|
|
||||||
per-folder local-path picker defaulting to `~/<name>`.
|
|
||||||
|
|
||||||
Refuse to attach into a non-empty directory without an explicit
|
|
||||||
`--allow-nonempty`, so the conflict-copy noise of §2.4 is opt-in rather than a
|
|
||||||
surprise.
|
|
||||||
|
|
||||||
### P3 — Back the profile up to Drive
|
|
||||||
|
|
||||||
**Problem:** modes, pins, mountpoint, and cache budget exist only on the dead
|
|
||||||
machine, so even a perfect P2 restores folders in the wrong mode with no pins.
|
|
||||||
|
|
||||||
Write a small JSON document into a hidden folder in the user's own Drive — say
|
|
||||||
`.proton-drive-linux/profile.json` — containing, per device:
|
|
||||||
|
|
||||||
- sync folders: remote uid, the local path last used, and mode
|
|
||||||
- pinned node uids
|
|
||||||
- mountpoint and cache budget
|
|
||||||
|
|
||||||
It rides on Drive's own encryption, so this introduces no new crypto and no new
|
|
||||||
place for plaintext to sit. Rewrite it whenever any of those change, debounced.
|
|
||||||
|
|
||||||
On a fresh install, after login: *"Found settings from `<device>`, last updated
|
|
||||||
`<date>`. Restore?"* Node uids are stable across machines, so pins restore
|
|
||||||
exactly. Local paths need remapping when the username or home differs — show the
|
|
||||||
old path, default the new one to `$HOME/<basename>`.
|
|
||||||
|
|
||||||
Store the last-used local path only as a *hint*. Restoring a profile must never
|
|
||||||
write to a path the user has not confirmed in this session.
|
|
||||||
|
|
||||||
### P4 — Make the unrecoverable visible
|
|
||||||
|
|
||||||
Nothing above helps with §1's undrained writes, because by then it is too late.
|
|
||||||
The mitigation is to make the pending queue impossible to miss *before* the
|
|
||||||
machine is lost:
|
|
||||||
|
|
||||||
- pending count and bytes in `pdfs status`, using the existing `pending` map and
|
|
||||||
`TransferRegistry`
|
|
||||||
- a tray icon state and a GUI banner while the queue is non-empty
|
|
||||||
- a warning on logout or shutdown while it is non-empty
|
|
||||||
|
|
||||||
### P5 — Verification
|
|
||||||
|
|
||||||
None of the above is trustworthy without exercising the actual shape of the
|
|
||||||
disaster. The end-to-end test is: register a device, sync a folder, upload
|
|
||||||
content, **delete the local state directory and cache entirely** (simulating the
|
|
||||||
lost machine), restart, restore, and assert the tree comes back byte-identical.
|
|
||||||
|
|
||||||
That is a two-account or at least two-state-dir integration test against a live
|
|
||||||
account, not a unit test. Until the mock-backend problem described in
|
|
||||||
`audit-plan.md` is solved, it stays manual — but it should be run, and its result
|
|
||||||
recorded here, before P1–P3 are called done.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Current status
|
|
||||||
|
|
||||||
| Phase | State |
|
|
||||||
|---|---|
|
|---|---|
|
||||||
| Manual runbook (§2) | Works, with the hostname and basename caveats |
|
| Pins are backed up but not restored | Mark files available offline again by hand |
|
||||||
| P1 explicit adoption | Not started |
|
| No warning at shutdown or sign-out while changes are queued | Check `pdfs sync queue` before you retire a machine |
|
||||||
| P2 discover + attach | Not started |
|
| The full disaster drill (delete all local state, restore, compare byte for byte) has not been run against a live account | The individual steps are tested; the whole sequence is not |
|
||||||
| P3 profile on Drive | Not started |
|
|
||||||
| P4 pending visibility | Not started |
|
Planned work is tracked in [ROADMAP.md](ROADMAP.md).
|
||||||
| P5 restore drill | Never run |
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# Roadmap
|
||||||
|
|
||||||
|
Planned work and open verification, as of 2.5.0. Individual defects and their status live in
|
||||||
|
[BUGS.md](BUGS.md), which is the authoritative ledger; this page groups what is left by theme.
|
||||||
|
Nothing here is a promise of a date.
|
||||||
|
|
||||||
|
- [Data safety and recovery](#data-safety-and-recovery)
|
||||||
|
- [Features](#features)
|
||||||
|
- [Performance and correctness](#performance-and-correctness)
|
||||||
|
- [Verification still owed](#verification-still-owed)
|
||||||
|
- [Release assurance](#release-assurance)
|
||||||
|
|
||||||
|
## Data safety and recovery
|
||||||
|
|
||||||
|
| Item | State | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| Protect queued changes at shutdown | Not started | A systemd inhibitor lock while `staging/` holds unsent data, a warning when signing out, and a blocking `pdfs sync flush`. Today the queue is visible through `pdfs sync queue`, the tray and **Sync → Overview**, but nothing stops a shutdown (B66) |
|
||||||
|
| Restore pins from the machine profile | Not started | `profile.json` records pins; `sync restore` does not apply them yet |
|
||||||
|
| Profile backup health | Not started | A failed profile upload is a `WARN` in the log and nothing in the UI (B68) |
|
||||||
|
| Rotated credentials survive a keyring failure | Open | Surface and retry keyring write failures so a rotated single-use refresh token is not lost (B61) |
|
||||||
|
| Encrypt local state at rest | Not started | SQLCipher for `cache.db` and an AEAD for the content cache, keyed from the keyring. `sdk_cache.db` is already encrypted. See [ARCHITECTURE.md §8](ARCHITECTURE.md#8-threat-model-what-this-client-writes-to-disk-in-plaintext) |
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
| Item | State | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| File-manager integration | Not started | Context-menu actions (available offline, share link, versions) and sync badges for Nautilus, Dolphin and Thunar |
|
||||||
|
| Terminal UI | Not started | `pdfs tui` with transfers, sync state and logs |
|
||||||
|
| Multiple accounts | Not started | Separate config, state and mountpoint per profile, one daemon each |
|
||||||
|
| Mount options | Not started | Read-only mount, `allow_other` |
|
||||||
|
| Symbolic links | Not started | Proton Drive has no link type; links would need a placeholder format. Until then, creating one fails with `EOPNOTSUPP` |
|
||||||
|
| Add someone else's photo to your album | Blocked | Needs `copyPhoto` in the SDK |
|
||||||
|
| Photo name search | Not started | Photos search by date and filters only |
|
||||||
|
| Automatic photo upload from a folder | Not started | |
|
||||||
|
| Faster Google Photos import | Not started | Uploads run one at a time. Trash folders of locales missing from `TRASH_FOLDERS` import as photos; motion-photo `.MP` parts are skipped |
|
||||||
|
| Content-addressed block cache | Idea | Store blocks by hash to deduplicate identical content |
|
||||||
|
|
||||||
|
## Performance and correctness
|
||||||
|
|
||||||
|
| Item | Notes |
|
||||||
|
|---|---|
|
||||||
|
| Saturating stream-ring accounting | `self.bytes -= …` in `crates/pdfs-fuse/src/reads.rs` should saturate so an accounting drift cannot wrap in release builds |
|
||||||
|
| Block geometry learned one read late | The first read of a file plans on 4 MiB blocks; accepted as a bounded cost (B87) |
|
||||||
|
| Path-based commands under secondary mounts | `pin`, `rename`, `move`, `rm` and sharing cannot name a path inside an on-demand synced folder yet (B88) |
|
||||||
|
| Unicode normalization | NFC and NFD names are distinct, deliberately (B36) |
|
||||||
|
| Pipelined node enumeration | Faster cold listings of large folders |
|
||||||
|
|
||||||
|
## Verification still owed
|
||||||
|
|
||||||
|
Fixes marked *unverified* in [BUGS.md](BUGS.md) have unit or offline coverage but have not been
|
||||||
|
driven against a live account. The larger outstanding runs:
|
||||||
|
|
||||||
|
- **Disaster-recovery drill.** Register a device, sync a folder, delete all local state, restore
|
||||||
|
with `pdfs sync restore` and compare the tree byte for byte. See [RECOVERY.md](RECOVERY.md).
|
||||||
|
- **Second-account sharing.** Shared trees on a foreign volume, accepting invitations, changing
|
||||||
|
roles, the viewer cases of `regression B34`, and an editor writing into someone else's share.
|
||||||
|
- **Fault injection.** Network loss mid-upload, a full disk during staging, and crash points
|
||||||
|
across the write publication path (after each fsync, rename and queue commit).
|
||||||
|
- **CAPTCHA sign-in** against a real verification request (B8).
|
||||||
|
- **Managed live mode matrix** before each release:
|
||||||
|
`scripts/fuse-acceptance.sh --managed-live <empty-a> <empty-b>`. See [TESTING.md](TESTING.md).
|
||||||
|
- **The overnight hang** (B91) is not diagnosed yet. The supervisor and `pdfs diagnostics` exist to
|
||||||
|
catch the next occurrence.
|
||||||
|
|
||||||
|
## Release assurance
|
||||||
|
|
||||||
|
| Item | State |
|
||||||
|
|---|---|
|
||||||
|
| Tag, workspace and PKGBUILD version agreement | Enforced by the release workflow |
|
||||||
|
| Clean-install test of the `.deb` and `.rpm` in a VM | Pending (B62) |
|
||||||
|
| Protocol version identity between front ends and daemon | Open (B63) |
|
||||||
|
| SBOM, provenance and checksums for release artifacts | Open (B65). The RPM is GPG-signed |
|
||||||
@@ -78,10 +78,9 @@ in-flight `.crdownload` must not be sealed as a revision) and B74 (a file
|
|||||||
renamed after close must keep its content) need a real mount and a reachable
|
renamed after close must keep its content) need a real mount and a reachable
|
||||||
daemon; they are skipped, not failed, without one.
|
daemon; they are skipped, not failed, without one.
|
||||||
|
|
||||||
B74 is currently **failing** — it is a live data-loss defect, not a regression
|
B74 was found by exactly this mechanism: the B70 case failed, and narrowing it
|
||||||
guard, and it was found by exactly this mechanism: the B70 case failed, and
|
produced a smaller reproduction that got its own case. The defect is fixed and
|
||||||
narrowing it produced a smaller reproduction that has its own case now. Expect a
|
live-validated (2026-07-26); the case now guards against its return.
|
||||||
red run against a real mount until it is fixed.
|
|
||||||
|
|
||||||
**B79** (an on-demand device folder accepts writes) discovers its target from
|
**B79** (an on-demand device folder accepts writes) discovers its target from
|
||||||
`pdfs locations --json` and skips when this machine has no mounted on-demand
|
`pdfs locations --json` and skips when this machine has no mounted on-demand
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
Start with `pdfs diagnose`, then find your symptom below. If nothing here helps,
|
||||||
|
[open an issue](https://github.com/narrrl/proton-drive-linux/issues/new/choose) with the
|
||||||
|
information listed under [Reporting a problem](#reporting-a-problem).
|
||||||
|
|
||||||
|
- [First checks](#first-checks)
|
||||||
|
- [Signing in](#signing-in)
|
||||||
|
- [The Proton Drive folder](#the-proton-drive-folder)
|
||||||
|
- [Uploads and sync](#uploads-and-sync)
|
||||||
|
- [Disk space](#disk-space)
|
||||||
|
- [Desktop integration](#desktop-integration)
|
||||||
|
- [Start over with a clean local state](#start-over-with-a-clean-local-state)
|
||||||
|
- [Reporting a problem](#reporting-a-problem)
|
||||||
|
|
||||||
|
## First checks
|
||||||
|
|
||||||
|
### `pdfs diagnose`
|
||||||
|
|
||||||
|
Checks the installation and prints a report. It does not need the service, because the state
|
||||||
|
worth diagnosing is often the one where the service will not start.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ pdfs diagnose
|
||||||
|
Paths
|
||||||
|
[ok ] state dir: /home/you/.local/state/proton-drive-linux
|
||||||
|
[ok ] cache dir: /home/you/.cache/proton-drive-linux
|
||||||
|
[ok ] state dir writable: write and fsync succeeded
|
||||||
|
[ok ] database: /home/you/.local/state/proton-drive-linux/cache.db (473.1 MiB)
|
||||||
|
|
||||||
|
FUSE
|
||||||
|
[ok ] /dev/fuse: /dev/fuse
|
||||||
|
[ok ] unmount helper: fusermount3 or fusermount on PATH
|
||||||
|
|
||||||
|
Account
|
||||||
|
[ok ] keyring session: you@proton.me
|
||||||
|
|
||||||
|
Daemon
|
||||||
|
[ok ] control socket: /home/you/.local/state/proton-drive-linux/control.sock
|
||||||
|
[ok ] daemon responding
|
||||||
|
[ok ] active mount: /home/you/ProtonDrive (fuse.protondrive)
|
||||||
|
[ok ] network: online
|
||||||
|
[ok ] queued writes: none
|
||||||
|
[ok ] database integrity: SQLite integrity_check passed
|
||||||
|
[ok ] configured mountpoint: /home/you/ProtonDrive
|
||||||
|
|
||||||
|
No problems found.
|
||||||
|
```
|
||||||
|
|
||||||
|
It exits non-zero when a check fails, so it also works in a health-check script.
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
journalctl --user -u proton-drive.service -f # follow the service
|
||||||
|
journalctl --user -u proton-drive.service -b -p warning # warnings since boot
|
||||||
|
```
|
||||||
|
|
||||||
|
For more detail, raise the log level with a drop-in (see
|
||||||
|
[CONFIGURATION.md](CONFIGURATION.md#the-systemd-service)) and restart the service.
|
||||||
|
|
||||||
|
### Service state
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user status proton-drive.service
|
||||||
|
pdfs status
|
||||||
|
```
|
||||||
|
|
||||||
|
## Signing in
|
||||||
|
|
||||||
|
**"Proton is asking for a CAPTCHA"** — Proton wants human verification, usually on a VPN or an
|
||||||
|
unfamiliar network. The CLI cannot show it. Sign in with `pdfs-app`, which displays the challenge;
|
||||||
|
the stored session then works for the CLI too.
|
||||||
|
|
||||||
|
**Sign-in works but is forgotten after a restart** — no Secret Service provider is running, or its
|
||||||
|
keyring is locked. Install and unlock GNOME Keyring, KWallet or KeePassXC (with Secret Service
|
||||||
|
enabled). `pdfs diagnose` shows `keyring session` as failing in that case.
|
||||||
|
|
||||||
|
## The Proton Drive folder
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> When the service hangs, any program that touches the Proton Drive folder can hang with it,
|
||||||
|
> including `ls` and your shell's prompt. Prefix exploratory commands with `timeout 5`.
|
||||||
|
|
||||||
|
**`Transport endpoint is not connected`** — the service stopped without unmounting. Unmount and
|
||||||
|
restart:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
fusermount3 -uz ~/ProtonDrive
|
||||||
|
systemctl --user restart proton-drive.service
|
||||||
|
```
|
||||||
|
|
||||||
|
**The folder is empty or missing** — check that the service runs and is signed in
|
||||||
|
(`pdfs status`). The service waits for a stored session before it mounts.
|
||||||
|
|
||||||
|
**Everything in the folder hangs** — find out what the service is doing, then restart it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
timeout 10 pdfs diagnostics
|
||||||
|
systemctl --user restart proton-drive.service
|
||||||
|
```
|
||||||
|
|
||||||
|
The service watchdog restarts a service that stops answering for two minutes by itself. Attach the
|
||||||
|
`pdfs diagnostics` output to a bug report; it shows which worker is stuck.
|
||||||
|
|
||||||
|
**A change made on another device does not show up** — changes arrive through Proton's event
|
||||||
|
stream, normally within seconds. To fetch a folder again at once:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pdfs refresh Documents
|
||||||
|
```
|
||||||
|
|
||||||
|
**"Permission denied" when writing** — the item is in a share where you are a viewer, or under
|
||||||
|
`Shared with me/`, which is read-only as a folder. Check your role with `pdfs shared-with-me`.
|
||||||
|
|
||||||
|
**A program says "Operation not supported"** — Proton Drive cannot store symbolic links, hard
|
||||||
|
links, device files or FIFOs. The error is intentional. Use a synced folder for
|
||||||
|
projects that need them, or exclude those paths.
|
||||||
|
|
||||||
|
## Uploads and sync
|
||||||
|
|
||||||
|
**Something is stuck uploading** — list the queue and retry:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pdfs sync queue
|
||||||
|
pdfs sync retry # every failed operation
|
||||||
|
pdfs sync retry 42 # one operation
|
||||||
|
```
|
||||||
|
|
||||||
|
The app shows the same list on **Sync → Overview**. An operation that keeps failing shows its
|
||||||
|
error there.
|
||||||
|
|
||||||
|
**A file in a synced folder does not upload** — check that:
|
||||||
|
|
||||||
|
- it is not matched by `.pdfsignore` or `ignore_patterns`;
|
||||||
|
- no program still holds it open for writing (such files wait until closed);
|
||||||
|
- sync is not paused (`pdfs status`, tray);
|
||||||
|
- the folder has no error on **Sync → Folders**.
|
||||||
|
|
||||||
|
`pdfs sync now` starts a pass immediately.
|
||||||
|
|
||||||
|
**Files named `(sync-conflict …)` appeared** — the file changed in two places at once. Nothing was
|
||||||
|
lost; see [Conflicts](USER_GUIDE.md#conflicts) to pick a version.
|
||||||
|
|
||||||
|
**Photos show the wrong date after a Google Takeout import** — run
|
||||||
|
`pdfs redate-photos --dry-run`, see [USER_GUIDE.md](USER_GUIDE.md#importing-from-google-photos).
|
||||||
|
|
||||||
|
## Disk space
|
||||||
|
|
||||||
|
| What | Check | Reduce |
|
||||||
|
|---|---|---|
|
||||||
|
| Content cache | `pdfs cache inspect` | Lower **Preferences → Storage → Cache size limit**, or `pdfs cache clear` |
|
||||||
|
| Available-offline files | `pdfs pins` | `pdfs unpin PATH` |
|
||||||
|
| Metadata database | `pdfs cache inspect` (reclaimable) | `pdfs cache vacuum` |
|
||||||
|
| Synced folders | `pdfs sync list` | Switch large folders to online only: `pdfs sync mode ID ondemand` |
|
||||||
|
|
||||||
|
`pdfs cache vacuum` locks the database while it runs and needs free space for a second copy of it.
|
||||||
|
|
||||||
|
## Desktop integration
|
||||||
|
|
||||||
|
**No tray icon on GNOME** — install and enable the AppIndicator and KStatusNotifierItem Support
|
||||||
|
extension. Other desktops show the icon natively. If you hid it from its own menu, turn it back on
|
||||||
|
under **Preferences → General → Appearance**.
|
||||||
|
|
||||||
|
**The search launcher does nothing** — `pdfs-prompt` needs the service. Run it from a terminal to
|
||||||
|
see its error. For `--fzf`, install `fzf` and a supported terminal.
|
||||||
|
|
||||||
|
**The app shows "Not responding"** — the service runs but does not answer within five seconds.
|
||||||
|
See [Everything in the folder hangs](#the-proton-drive-folder).
|
||||||
|
|
||||||
|
## Start over with a clean local state
|
||||||
|
|
||||||
|
This removes the local database and cache so they are rebuilt from Proton Drive. It is the fix for
|
||||||
|
a damaged database and the only way to downgrade to an older release.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Content in `staging/` and `recovery/` has not reached Proton Drive yet. Deleting it loses those
|
||||||
|
> changes permanently. Wait until `pdfs sync queue` is empty.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pdfs sync queue # must list nothing
|
||||||
|
systemctl --user stop proton-drive.service
|
||||||
|
mv ~/.local/state/proton-drive-linux ~/.local/state/proton-drive-linux.bak
|
||||||
|
mv ~/.cache/proton-drive-linux ~/.cache/proton-drive-linux.bak
|
||||||
|
systemctl --user start proton-drive.service
|
||||||
|
```
|
||||||
|
|
||||||
|
Your session is in the keyring and your settings in `~/.config`, so you stay signed in. Synced
|
||||||
|
folders are not re-attached automatically; restore them with `pdfs sync restore` as described in
|
||||||
|
[RECOVERY.md](RECOVERY.md#2-restore-your-synced-folders). Delete the `.bak` directories once
|
||||||
|
everything is back.
|
||||||
|
|
||||||
|
## Reporting a problem
|
||||||
|
|
||||||
|
Include:
|
||||||
|
|
||||||
|
- `pdfs --version`, your distribution and desktop;
|
||||||
|
- the output of `pdfs diagnose`;
|
||||||
|
- relevant log lines from `journalctl --user -u proton-drive.service`;
|
||||||
|
- for a hang, the output of `timeout 10 pdfs diagnostics`;
|
||||||
|
- the steps that reproduce it.
|
||||||
|
|
||||||
|
Logs contain file names. Remove anything private before posting. Report security problems
|
||||||
|
privately as described in [SECURITY.md](../SECURITY.md).
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
# User guide
|
||||||
|
|
||||||
|
How Proton Drive for Linux works and how to use it day to day. For installation see
|
||||||
|
[INSTALL.md](INSTALL.md); every command mentioned here is described in [CLI.md](CLI.md).
|
||||||
|
|
||||||
|
- [How it works](#how-it-works)
|
||||||
|
- [My files](#my-files)
|
||||||
|
- [Keeping files on this computer](#keeping-files-on-this-computer)
|
||||||
|
- [Syncing local folders](#syncing-local-folders)
|
||||||
|
- [Conflicts](#conflicts)
|
||||||
|
- [Pausing and limiting sync](#pausing-and-limiting-sync)
|
||||||
|
- [Photos](#photos)
|
||||||
|
- [Sharing](#sharing)
|
||||||
|
- [Trash and version history](#trash-and-version-history)
|
||||||
|
- [Computers](#computers)
|
||||||
|
- [Search launcher](#search-launcher)
|
||||||
|
- [Choosing how files open](#choosing-how-files-open)
|
||||||
|
- [Tray](#tray)
|
||||||
|
- [Preferences](#preferences)
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
A background service, `proton-drive.service`, runs `pdfs daemon`. It holds your session, talks to
|
||||||
|
Proton Drive, and keeps your Drive mounted as a folder. Everything else is a front end that asks
|
||||||
|
the service to do things over a local socket:
|
||||||
|
|
||||||
|
| Program | What it is |
|
||||||
|
|---|---|
|
||||||
|
| `pdfs-app` | The desktop app: files, photos, sharing, sync, preferences |
|
||||||
|
| `pdfs-tray` | Tray icon with status, pause and quick actions |
|
||||||
|
| `pdfs-prompt` | A search launcher for a keyboard shortcut |
|
||||||
|
| `pdfs` | The command-line client, for everything above and for scripts |
|
||||||
|
|
||||||
|
Your Drive, your file names and your photos are end-to-end encrypted by Proton. On this computer,
|
||||||
|
the service must decrypt what it shows you, and it keeps a decrypted cache and index on disk. Use
|
||||||
|
full-disk or home-directory encryption. [ARCHITECTURE.md §8](ARCHITECTURE.md#8-threat-model-what-this-client-writes-to-disk-in-plaintext)
|
||||||
|
lists exactly what is stored.
|
||||||
|
|
||||||
|
The app uses these words consistently, and so does this guide:
|
||||||
|
|
||||||
|
| Term | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| **My files** | The root of your Drive |
|
||||||
|
| **Proton Drive folder** | Where My files appears on disk, `~/ProtonDrive` by default |
|
||||||
|
| **Online only** | A file or folder whose content downloads when opened |
|
||||||
|
| **Available offline** | A single file kept on this computer |
|
||||||
|
| **Synced folder** | A local folder with a full copy on this computer, backed up to Proton Drive |
|
||||||
|
| **Proton Drive service** | The background service (`proton-drive.service`) |
|
||||||
|
|
||||||
|
## My files
|
||||||
|
|
||||||
|
My files is mounted at `~/ProtonDrive`. Every file and folder appears immediately, but content
|
||||||
|
downloads only when a program reads it:
|
||||||
|
|
||||||
|
- **Reads fetch only what is needed.** A video player that seeks, or a tool that reads a file
|
||||||
|
header, downloads only those parts. Media streams while it plays.
|
||||||
|
- **Writes are safe offline.** When a program closes a file it wrote, the service stores the new
|
||||||
|
content on disk and queues the upload. The queue survives network loss, restarts and crashes.
|
||||||
|
`pdfs sync queue` lists what has not reached Proton Drive yet.
|
||||||
|
- **Recently read content is cached** on disk up to the cache size limit (5 GiB by default) and
|
||||||
|
evicted oldest first.
|
||||||
|
- **Items shared with you** appear under `Shared with me/` inside the Proton Drive folder. Shares
|
||||||
|
where you are a viewer are read-only.
|
||||||
|
|
||||||
|
The **My files** page of the app browses the same tree without going through the mount. It has a
|
||||||
|
grid and a list view, sorting, thumbnails (including camera RAW previews), multi-select and drag
|
||||||
|
and drop upload. **Details** (Alt+Enter) shows size, dates, sharing and version history for the
|
||||||
|
selected item.
|
||||||
|
|
||||||
|
Symbolic links, hard links, device files and FIFOs cannot be stored on Proton Drive. Programs that
|
||||||
|
try to create them get a clean error, not a hang or `EIO`.
|
||||||
|
|
||||||
|
## Keeping files on this computer
|
||||||
|
|
||||||
|
Turn on **Make available offline** for a file in My files, or run `pdfs pin <path>`. The file is
|
||||||
|
downloaded completely and never evicted from the cache. `pdfs pins` lists these files, and
|
||||||
|
**Preferences → Storage → Available offline** shows them in the app.
|
||||||
|
|
||||||
|
Turning it off (`pdfs unpin <path>`) frees the space. The file stays on Proton Drive.
|
||||||
|
|
||||||
|
## Syncing local folders
|
||||||
|
|
||||||
|
A synced folder is an ordinary folder on this computer, for example `~/Documents`, that is
|
||||||
|
backed up to Proton Drive under this computer's name. It appears on the **Computers** page of the
|
||||||
|
app and in the web app.
|
||||||
|
|
||||||
|
- Add one with **Add Folder to Sync** on the **Computers** page, or `pdfs sync add ~/Documents`.
|
||||||
|
- Changes flow both ways. Local edits upload a few seconds after the file is closed; remote edits
|
||||||
|
download on the next pass.
|
||||||
|
- A file that another program still holds open for writing is not uploaded until it is closed.
|
||||||
|
- **Sync → Folders** in the app, and `pdfs sync list`, show every folder and its state.
|
||||||
|
|
||||||
|
Each synced folder can also be switched to **Online only**. The local copy is replaced by a mount
|
||||||
|
of the folder, so the files stay visible but take no disk space until opened. Switch with the
|
||||||
|
**Synced / Online only** switch on the folder's card, or `pdfs sync mode <id> ondemand` and back
|
||||||
|
with `pdfs sync mode <id> mirror`. `pdfs locations` lists every local place the service occupies.
|
||||||
|
|
||||||
|
Removing a synced folder (**Stop Syncing** or `pdfs sync rm <id>`) keeps the local files and the
|
||||||
|
copy on Proton Drive. Add `--delete-remote` (or tick **Also delete from Proton Drive**) to delete
|
||||||
|
the copy on Proton Drive as well.
|
||||||
|
|
||||||
|
### Excluding files with `.pdfsignore`
|
||||||
|
|
||||||
|
Synced folders skip build output, dependency folders and editor leftovers. Rules come from two
|
||||||
|
places, and both apply:
|
||||||
|
|
||||||
|
1. A `.pdfsignore` file at the root of the synced folder (`.protonignore` also works), using
|
||||||
|
gitignore syntax, including `!` to re-include:
|
||||||
|
|
||||||
|
```gitignore
|
||||||
|
build/
|
||||||
|
*.log
|
||||||
|
!important.log
|
||||||
|
```
|
||||||
|
|
||||||
|
2. The global `ignore_patterns` list in `config.json` (see
|
||||||
|
[CONFIGURATION.md](CONFIGURATION.md#ignore_patterns)). When unset, it defaults to `.git/`,
|
||||||
|
`.hg/`, `.svn/`, `node_modules/`, `target/`, `.venv/`, `__pycache__/`, `*~`, `*.swp`, `*.tmp`,
|
||||||
|
`.DS_Store` and `Thumbs.db`.
|
||||||
|
|
||||||
|
Rules are re-read on every pass. Ignoring never deletes anything: a file that becomes ignored is
|
||||||
|
simply no longer tracked, and its copy on Proton Drive stays.
|
||||||
|
|
||||||
|
## Conflicts
|
||||||
|
|
||||||
|
When the same file changed on this computer and somewhere else since the last sync, neither side
|
||||||
|
is thrown away. The local version is kept as a copy named like
|
||||||
|
`report (sync-conflict 1759068180).odt`, where the number is the time of the conflict in seconds
|
||||||
|
since 1970, and the remote version takes the original name.
|
||||||
|
|
||||||
|
Conflicts appear under **Sync → Overview → Needs attention** and in `pdfs conflicts`. For each
|
||||||
|
one, keep the original, keep the copy, or keep both under a new name:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pdfs conflicts
|
||||||
|
pdfs conflicts resolve "Documents/report (sync-conflict 1759068180).odt" --keep copy
|
||||||
|
```
|
||||||
|
|
||||||
|
The version you do not keep goes to the Trash, so a wrong choice can be undone.
|
||||||
|
|
||||||
|
## Pausing and limiting sync
|
||||||
|
|
||||||
|
- **Pause** from the tray, the Sync page, or `pdfs sync pause`. Uploads and synced folders wait;
|
||||||
|
reading files keeps working. `pdfs sync resume` continues.
|
||||||
|
- **Limit bandwidth** under **Preferences → General → Network**, or with
|
||||||
|
`pdfs sync limit --up 2M --down 10M`. `0` removes a limit.
|
||||||
|
- **Retry** a failed upload from **Sync → Overview**, or with `pdfs sync retry`.
|
||||||
|
|
||||||
|
## Photos
|
||||||
|
|
||||||
|
The **Photos** page shows your Proton Photos library. Photos are stored separately from My files
|
||||||
|
and do not appear in the Proton Drive folder.
|
||||||
|
|
||||||
|
- **Timeline** groups photos by day, month or year as you zoom. Drag the scrubber on the right edge
|
||||||
|
to jump through the years. **On this day** shows photos from the same date in earlier years.
|
||||||
|
- **Filters** narrow the timeline to images, videos or RAW files, to favorites, or to photos that
|
||||||
|
are in no album.
|
||||||
|
- **RAW + JPEG pairs** of the same shot show as one tile. The viewer switches between them, and
|
||||||
|
moving the tile to the Trash removes both.
|
||||||
|
- **Albums** lists your albums and albums shared with you. Create, rename and delete albums, and
|
||||||
|
add or remove photos.
|
||||||
|
- **Places** groups photos by the town they were taken in, as cards or on a map. Towns come from
|
||||||
|
bundled [GeoNames](https://www.geonames.org/) data (CC BY 4.0) and the map from
|
||||||
|
[Natural Earth](https://www.naturalearthdata.com/) (public domain), so nothing is fetched online.
|
||||||
|
An optional street map from [OpenFreeMap](https://openfreemap.org/) (© OpenStreetMap
|
||||||
|
contributors) can be enabled under **Preferences → General → Appearance**. It is off by default
|
||||||
|
because the tile server learns which areas you look at.
|
||||||
|
- **Viewer**: favorites, EXIF details, show on map, slideshow (F5).
|
||||||
|
- **Find Duplicates…** in the page menu lists byte-identical copies. The **Similar** switch also
|
||||||
|
finds resized or re-encoded copies and burst shots by comparing thumbnails.
|
||||||
|
- **Upload** adds photos. Its menu holds **Import from Google Photos…**.
|
||||||
|
|
||||||
|
### Importing from Google Photos
|
||||||
|
|
||||||
|
Request a Google Takeout export of Google Photos, then pass every `.zip` of it at once. A photo's
|
||||||
|
metadata often sits in a different part of the export than the photo itself.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pdfs import-google-photos --dry-run ~/Downloads/takeout-*.zip # see what would happen
|
||||||
|
pdfs import-google-photos --wait ~/Downloads/takeout-*.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Albums are recreated, capture dates and locations are taken from the metadata, and photos already
|
||||||
|
on the account are skipped, so an interrupted import can be run again. `pdfs import-status` and
|
||||||
|
`pdfs cancel-import` follow or stop an import that runs in the background.
|
||||||
|
|
||||||
|
If photos from an earlier import are filed under the day they were uploaded,
|
||||||
|
`pdfs redate-photos --dry-run` finds those whose file name carries a different date, and
|
||||||
|
`pdfs redate-photos` fixes them. Proton cannot change a capture date in place, so each fix
|
||||||
|
re-uploads the photo and moves the original to the Trash, keeping favorites and albums.
|
||||||
|
|
||||||
|
## Sharing
|
||||||
|
|
||||||
|
- **Share…** on a file or folder invites people by email as viewer or editor, or creates a public
|
||||||
|
link with an optional password and expiry. `pdfs share`, `pdfs members`, `pdfs share-role`,
|
||||||
|
`pdfs unshare` and `pdfs public-link` do the same from a terminal.
|
||||||
|
- **Shared by me** lists everything you share, filtered by links, people or expired links. Copy a
|
||||||
|
live link or stop several shares at once.
|
||||||
|
- **Shared with me** lists what others share with you, with who shared it and your access.
|
||||||
|
Pending invitations wait in a banner; `pdfs invitations` accepts or rejects them. Saved public
|
||||||
|
links (`pdfs bookmarks`) have their own view.
|
||||||
|
|
||||||
|
## Trash and version history
|
||||||
|
|
||||||
|
The **Trash** page lists trashed items with the date they were deleted. Restore puts an item back
|
||||||
|
where it was, together with trashed folders above it. **Delete Forever** and **Empty Trash** are
|
||||||
|
permanent. The CLI equivalents are `pdfs trash`, `pdfs restore`, `pdfs delete-forever` and
|
||||||
|
`pdfs empty-trash`.
|
||||||
|
|
||||||
|
Proton Drive keeps earlier versions of every file. **Details → Versions…** in the app, or
|
||||||
|
`pdfs versions list <path>`, shows them. You can:
|
||||||
|
|
||||||
|
- **Restore** a version. This happens on the server; nothing is re-uploaded.
|
||||||
|
- **Save** a version to a separate local file without touching the current one.
|
||||||
|
- **Delete** an old version permanently. The current version cannot be deleted.
|
||||||
|
|
||||||
|
## Computers
|
||||||
|
|
||||||
|
**Computers** lists this computer and every other computer that backs up to your account.
|
||||||
|
|
||||||
|
- **This computer** shows its synced folders with their state, and **Restore Folders…** brings
|
||||||
|
back folders that are backed up under it but not synced here yet.
|
||||||
|
- **Another computer** can be browsed read-only in the same list or grid as My files, and
|
||||||
|
**Restore to This Computer…** downloads its folders here.
|
||||||
|
- **Continue This Backup Here…** makes this computer take over another computer's backup, for
|
||||||
|
example after reinstalling or replacing a machine. See [RECOVERY.md](RECOVERY.md).
|
||||||
|
|
||||||
|
## Search launcher
|
||||||
|
|
||||||
|
`pdfs-prompt` searches Proton Drive and your home folder together, in one ranked list. It
|
||||||
|
tolerates prefixes, abbreviations, typos and swapped letters, and matches on parent folder names
|
||||||
|
as well. Bind it to a keyboard shortcut in your desktop settings.
|
||||||
|
|
||||||
|
The window stays loaded after the first use, so later shortcuts open it instantly. Folders, audio
|
||||||
|
and video open straight from the Proton Drive folder, so players can start before the whole file
|
||||||
|
downloads. Other Drive files download first, then open.
|
||||||
|
|
||||||
|
### Using your own launcher
|
||||||
|
|
||||||
|
`pdfs-prompt --dmenu` shows the results in fuzzel, rofi, wofi, tofi, bemenu or dmenu, whichever is
|
||||||
|
installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pdfs-prompt --dmenu
|
||||||
|
pdfs-prompt --dmenu --menu 'fuzzel --dmenu --width 60'
|
||||||
|
pdfs-prompt --dmenu --query invoice
|
||||||
|
```
|
||||||
|
|
||||||
|
A dmenu-style launcher filters a fixed list and cannot ask for new results per keystroke, so the
|
||||||
|
search takes two steps. The first list, `Search Drive ›`, shows your available-offline files; type
|
||||||
|
a query and press Enter to search. The second, `Drive: invoice ›`, shows the results; Enter opens
|
||||||
|
one, and typing something else searches again.
|
||||||
|
|
||||||
|
### Search as you type with fzf
|
||||||
|
|
||||||
|
`pdfs-prompt --fzf` re-queries the service on every keystroke, like the built-in window. When
|
||||||
|
started from a keybinding without a terminal, it opens one itself (foot, ghostty, kitty,
|
||||||
|
alacritty, wezterm or xterm) with the app ID `pdfs-prompt`, so one window rule covers all of them.
|
||||||
|
For Hyprland:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
windowrulev2 = float, class:^(pdfs-prompt)$
|
||||||
|
windowrulev2 = size 900 500, class:^(pdfs-prompt)$
|
||||||
|
windowrulev2 = center, class:^(pdfs-prompt)$
|
||||||
|
bind = SUPER, space, exec, pdfs-prompt --fzf
|
||||||
|
```
|
||||||
|
|
||||||
|
To make `--dmenu` or `--fzf` the default without changing your keybinding, set `prompt.mode` in
|
||||||
|
`config.json` ([CONFIGURATION.md](CONFIGURATION.md#prompt)). `--gtk` forces the built-in window.
|
||||||
|
|
||||||
|
## Choosing how files open
|
||||||
|
|
||||||
|
Files open with `xdg-open` by default. The `open_with` section of `config.json` overrides that per
|
||||||
|
file type, for example text in Neovim inside a terminal. The rules apply to the search launcher
|
||||||
|
and to the app. See [CONFIGURATION.md](CONFIGURATION.md#open_with).
|
||||||
|
|
||||||
|
## Tray
|
||||||
|
|
||||||
|
`pdfs-tray` starts at login and shows whether files are up to date, syncing, paused or offline.
|
||||||
|
Its menu opens the app or the Proton Drive folder, pauses or resumes sync, and stops or connects
|
||||||
|
the service. **Hide Tray Icon** removes it until you turn it back on under
|
||||||
|
**Preferences → General → Appearance → Show tray icon**.
|
||||||
|
|
||||||
|
GNOME needs the AppIndicator extension to show tray icons.
|
||||||
|
|
||||||
|
## Preferences
|
||||||
|
|
||||||
|
| Page | Settings |
|
||||||
|
|---|---|
|
||||||
|
| **General** | Start on login · Proton Drive folder location · upload and download limits · Proton theme, street map for Places, tray icon · language |
|
||||||
|
| **Storage** | Cache size limit · clear the cache · files kept available offline |
|
||||||
|
|
||||||
|
The account menu shows storage used and holds **Sign Out…**. Settings not exposed in the app are
|
||||||
|
listed in [CONFIGURATION.md](CONFIGURATION.md).
|
||||||
|
Before Width: | Height: | Size: 197 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 899 KiB After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 71 KiB |
@@ -9,7 +9,7 @@
|
|||||||
# Build + install: cd packaging && makepkg -fi
|
# Build + install: cd packaging && makepkg -fi
|
||||||
|
|
||||||
pkgname=proton-drive-linux
|
pkgname=proton-drive-linux
|
||||||
pkgver=2.5.0
|
pkgver=2.6.0
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Unofficial Proton Drive client for Linux: files-on-demand FUSE mount, CLI, GTK4 app + tray"
|
pkgdesc="Unofficial Proton Drive client for Linux: files-on-demand FUSE mount, CLI, GTK4 app + tray"
|
||||||
arch=('x86_64')
|
arch=('x86_64')
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
pkgbase = proton-drive-for-linux-bin
|
pkgbase = proton-drive-for-linux-bin
|
||||||
pkgdesc = Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (binaries)
|
pkgdesc = Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (binaries)
|
||||||
pkgver = 2.4.0
|
pkgver = 2.5.0
|
||||||
pkgrel = 1
|
pkgrel = 1
|
||||||
url = https://github.com/narrrl/proton-drive-linux
|
url = https://github.com/narrrl/proton-drive-linux
|
||||||
arch = x86_64
|
arch = x86_64
|
||||||
@@ -21,21 +21,21 @@ pkgbase = proton-drive-for-linux-bin
|
|||||||
optdepends = xdg-utils: "Open folder" action in the tray menu
|
optdepends = xdg-utils: "Open folder" action in the tray menu
|
||||||
optdepends = fzf: search-as-you-type launcher (pdfs-prompt --fzf)
|
optdepends = fzf: search-as-you-type launcher (pdfs-prompt --fzf)
|
||||||
optdepends = foot: terminal that hosts pdfs-prompt --fzf when started from a keybinding
|
optdepends = foot: terminal that hosts pdfs-prompt --fzf when started from a keybinding
|
||||||
provides = proton-drive-for-linux=2.4.0
|
provides = proton-drive-for-linux=2.5.0
|
||||||
provides = proton-drive-linux=2.4.0
|
provides = proton-drive-linux=2.5.0
|
||||||
provides = pdfs
|
provides = pdfs
|
||||||
conflicts = proton-drive-linux
|
conflicts = proton-drive-linux
|
||||||
conflicts = proton-drive-for-linux
|
conflicts = proton-drive-for-linux
|
||||||
conflicts = proton-drive-for-linux-git
|
conflicts = proton-drive-for-linux-git
|
||||||
options = !strip
|
options = !strip
|
||||||
options = !debug
|
options = !debug
|
||||||
source = proton-drive-linux-2.4.0-x86_64.tar.gz::https://github.com/narrrl/proton-drive-linux/releases/download/v2.4.0/proton-drive-linux-2.4.0-x86_64.tar.gz
|
source = proton-drive-linux-2.5.0-x86_64.tar.gz::https://github.com/narrrl/proton-drive-linux/releases/download/v2.5.0/proton-drive-linux-2.5.0-x86_64.tar.gz
|
||||||
source = io.narl.proton-drive-linux.desktop::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.4.0/packaging/io.narl.proton-drive-linux.desktop
|
source = io.narl.proton-drive-linux.desktop::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.5.0/packaging/io.narl.proton-drive-linux.desktop
|
||||||
source = io.narl.proton-drive-linux-tray.desktop::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.4.0/packaging/io.narl.proton-drive-linux-tray.desktop
|
source = io.narl.proton-drive-linux-tray.desktop::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.5.0/packaging/io.narl.proton-drive-linux-tray.desktop
|
||||||
source = io.narl.proton-drive-linux.svg::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.4.0/packaging/io.narl.proton-drive-linux.svg
|
source = io.narl.proton-drive-linux.svg::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.5.0/packaging/io.narl.proton-drive-linux.svg
|
||||||
source = proton-drive.service::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.4.0/packaging/proton-drive.service
|
source = proton-drive.service::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.5.0/packaging/proton-drive.service
|
||||||
source = LICENSE-2.4.0::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.4.0/LICENSE
|
source = LICENSE-2.5.0::https://raw.githubusercontent.com/narrrl/proton-drive-linux/v2.5.0/LICENSE
|
||||||
sha256sums = 00446aa9016466827bb0fe5ea1e1d171f478ab4194845f9005b690a05543fcca
|
sha256sums = 44ee9549dc14260de7e882f810911b253aaf5c83dc70537c0aeeae3769d30ce9
|
||||||
sha256sums = d190d6771fff0b975271fb62fd3fb0fa8bbf17d7278c10853c18557855b0c123
|
sha256sums = d190d6771fff0b975271fb62fd3fb0fa8bbf17d7278c10853c18557855b0c123
|
||||||
sha256sums = 1d95e250370220e3bb6aead6ce34d1d6cc759f42c4803b75f1667e78ff47c6c0
|
sha256sums = 1d95e250370220e3bb6aead6ce34d1d6cc759f42c4803b75f1667e78ff47c6c0
|
||||||
sha256sums = e598a93c2715c3a9fdc7fb937902083d4a1135e8fbdfe6fe7e37f0383ebbd828
|
sha256sums = e598a93c2715c3a9fdc7fb937902083d4a1135e8fbdfe6fe7e37f0383ebbd828
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Maintainer: Nils Pukropp <contact@narl.io>
|
# Maintainer: Nils Pukropp <contact@narl.io>
|
||||||
|
|
||||||
pkgname=proton-drive-for-linux-bin
|
pkgname=proton-drive-for-linux-bin
|
||||||
pkgver=2.4.0
|
pkgver=2.5.0
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (binaries)"
|
pkgdesc="Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (binaries)"
|
||||||
arch=('x86_64')
|
arch=('x86_64')
|
||||||
@@ -31,7 +31,7 @@ source=("proton-drive-linux-$pkgver-x86_64.tar.gz::$url/releases/download/$_tag/
|
|||||||
"io.narl.proton-drive-linux.svg::$_raw/packaging/io.narl.proton-drive-linux.svg"
|
"io.narl.proton-drive-linux.svg::$_raw/packaging/io.narl.proton-drive-linux.svg"
|
||||||
"proton-drive.service::$_raw/packaging/proton-drive.service"
|
"proton-drive.service::$_raw/packaging/proton-drive.service"
|
||||||
"LICENSE-$pkgver::$_raw/LICENSE")
|
"LICENSE-$pkgver::$_raw/LICENSE")
|
||||||
sha256sums=('00446aa9016466827bb0fe5ea1e1d171f478ab4194845f9005b690a05543fcca'
|
sha256sums=('44ee9549dc14260de7e882f810911b253aaf5c83dc70537c0aeeae3769d30ce9'
|
||||||
'd190d6771fff0b975271fb62fd3fb0fa8bbf17d7278c10853c18557855b0c123'
|
'd190d6771fff0b975271fb62fd3fb0fa8bbf17d7278c10853c18557855b0c123'
|
||||||
'1d95e250370220e3bb6aead6ce34d1d6cc759f42c4803b75f1667e78ff47c6c0'
|
'1d95e250370220e3bb6aead6ce34d1d6cc759f42c4803b75f1667e78ff47c6c0'
|
||||||
'e598a93c2715c3a9fdc7fb937902083d4a1135e8fbdfe6fe7e37f0383ebbd828'
|
'e598a93c2715c3a9fdc7fb937902083d4a1135e8fbdfe6fe7e37f0383ebbd828'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
pkgbase = proton-drive-for-linux-git
|
pkgbase = proton-drive-for-linux-git
|
||||||
pkgdesc = Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (git)
|
pkgdesc = Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (git)
|
||||||
pkgver = 2.4.0.r0.gda35a05
|
pkgver = 2.5.0.r0.g61266fc
|
||||||
pkgrel = 1
|
pkgrel = 1
|
||||||
url = https://github.com/narrrl/proton-drive-linux
|
url = https://github.com/narrrl/proton-drive-linux
|
||||||
arch = x86_64
|
arch = x86_64
|
||||||
@@ -24,8 +24,8 @@ pkgbase = proton-drive-for-linux-git
|
|||||||
optdepends = xdg-utils: "Open folder" action in the tray menu
|
optdepends = xdg-utils: "Open folder" action in the tray menu
|
||||||
optdepends = fzf: search-as-you-type launcher (pdfs-prompt --fzf)
|
optdepends = fzf: search-as-you-type launcher (pdfs-prompt --fzf)
|
||||||
optdepends = foot: terminal that hosts pdfs-prompt --fzf when started from a keybinding
|
optdepends = foot: terminal that hosts pdfs-prompt --fzf when started from a keybinding
|
||||||
provides = proton-drive-for-linux=2.4.0.r0.gda35a05
|
provides = proton-drive-for-linux=2.5.0.r0.g61266fc
|
||||||
provides = proton-drive-linux=2.4.0.r0.gda35a05
|
provides = proton-drive-linux=2.5.0.r0.g61266fc
|
||||||
provides = pdfs
|
provides = pdfs
|
||||||
conflicts = proton-drive-linux
|
conflicts = proton-drive-linux
|
||||||
conflicts = proton-drive-for-linux
|
conflicts = proton-drive-for-linux
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Maintainer: Nils Pukropp <contact@narl.io>
|
# Maintainer: Nils Pukropp <contact@narl.io>
|
||||||
|
|
||||||
pkgname=proton-drive-for-linux-git
|
pkgname=proton-drive-for-linux-git
|
||||||
pkgver=2.4.0.r0.gda35a05
|
pkgver=2.5.0.r0.g61266fc
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (git)"
|
pkgdesc="Unofficial Proton Drive client: FUSE files-on-demand mount, CLI, GTK4 app, tray (git)"
|
||||||
arch=('x86_64')
|
arch=('x86_64')
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
pkgbase = proton-drive-for-linux
|
pkgbase = proton-drive-for-linux
|
||||||
pkgdesc = Unofficial Proton Drive client: files-on-demand FUSE mount, CLI, GTK4 app and tray
|
pkgdesc = Unofficial Proton Drive client: files-on-demand FUSE mount, CLI, GTK4 app and tray
|
||||||
pkgver = 2.4.0
|
pkgver = 2.5.0
|
||||||
pkgrel = 1
|
pkgrel = 1
|
||||||
url = https://github.com/narrrl/proton-drive-linux
|
url = https://github.com/narrrl/proton-drive-linux
|
||||||
arch = x86_64
|
arch = x86_64
|
||||||
@@ -23,13 +23,13 @@ pkgbase = proton-drive-for-linux
|
|||||||
optdepends = xdg-utils: "Open folder" action in the tray menu
|
optdepends = xdg-utils: "Open folder" action in the tray menu
|
||||||
optdepends = fzf: search-as-you-type launcher (pdfs-prompt --fzf)
|
optdepends = fzf: search-as-you-type launcher (pdfs-prompt --fzf)
|
||||||
optdepends = foot: terminal that hosts pdfs-prompt --fzf when started from a keybinding
|
optdepends = foot: terminal that hosts pdfs-prompt --fzf when started from a keybinding
|
||||||
provides = proton-drive-linux=2.4.0
|
provides = proton-drive-linux=2.5.0
|
||||||
provides = pdfs
|
provides = pdfs
|
||||||
conflicts = proton-drive-linux
|
conflicts = proton-drive-linux
|
||||||
conflicts = proton-drive-for-linux-bin
|
conflicts = proton-drive-for-linux-bin
|
||||||
conflicts = proton-drive-for-linux-git
|
conflicts = proton-drive-for-linux-git
|
||||||
options = !lto
|
options = !lto
|
||||||
source = proton-drive-for-linux-2.4.0.tar.gz::https://github.com/narrrl/proton-drive-linux/archive/refs/tags/v2.4.0.tar.gz
|
source = proton-drive-for-linux-2.5.0.tar.gz::https://github.com/narrrl/proton-drive-linux/archive/refs/tags/v2.5.0.tar.gz
|
||||||
sha256sums = ae0f6daec257e9f7ba464ed6385bab7ac99f4247db0176296b2a58f703c24799
|
sha256sums = 2872352325204a9d9af31c12e7a793f4546ea0e5426dfb7b4f14a2b17af7c773
|
||||||
|
|
||||||
pkgname = proton-drive-for-linux
|
pkgname = proton-drive-for-linux
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Maintainer: Nils Pukropp <contact@narl.io>
|
# Maintainer: Nils Pukropp <contact@narl.io>
|
||||||
|
|
||||||
pkgname=proton-drive-for-linux
|
pkgname=proton-drive-for-linux
|
||||||
pkgver=2.4.0
|
pkgver=2.5.0
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Unofficial Proton Drive client: files-on-demand FUSE mount, CLI, GTK4 app and tray"
|
pkgdesc="Unofficial Proton Drive client: files-on-demand FUSE mount, CLI, GTK4 app and tray"
|
||||||
arch=('x86_64')
|
arch=('x86_64')
|
||||||
@@ -23,7 +23,7 @@ conflicts=('proton-drive-linux' 'proton-drive-for-linux-bin' 'proton-drive-for-l
|
|||||||
# LTO has broken GTK/Rust links for this workspace; the release profile keeps it off.
|
# LTO has broken GTK/Rust links for this workspace; the release profile keeps it off.
|
||||||
options=('!lto')
|
options=('!lto')
|
||||||
source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz")
|
source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz")
|
||||||
sha256sums=('ae0f6daec257e9f7ba464ed6385bab7ac99f4247db0176296b2a58f703c24799')
|
sha256sums=('2872352325204a9d9af31c12e7a793f4546ea0e5426dfb7b4f14a2b17af7c773')
|
||||||
|
|
||||||
_srcname="proton-drive-linux-$pkgver"
|
_srcname="proton-drive-linux-$pkgver"
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ crates/pdfs-gui/src/app/widgets/thumbnails.rs
|
|||||||
crates/pdfs-gui/src/app/widgets/versions_dialog.rs
|
crates/pdfs-gui/src/app/widgets/versions_dialog.rs
|
||||||
crates/pdfs-gui/src/dmenu.rs
|
crates/pdfs-gui/src/dmenu.rs
|
||||||
crates/pdfs-gui/src/fzf.rs
|
crates/pdfs-gui/src/fzf.rs
|
||||||
|
crates/pdfs-gui/src/hud/mod.rs
|
||||||
|
crates/pdfs-gui/src/hud/preview.rs
|
||||||
crates/pdfs-gui/src/i18n.rs
|
crates/pdfs-gui/src/i18n.rs
|
||||||
crates/pdfs-gui/src/main.rs
|
crates/pdfs-gui/src/main.rs
|
||||||
crates/pdfs-gui/src/prompt.rs
|
crates/pdfs-gui/src/prompt.rs
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: proton-drive-linux 2.1.0\n"
|
"Project-Id-Version: proton-drive-linux 2.1.0\n"
|
||||||
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
||||||
"POT-Creation-Date: 2026-09-28 16:17+0200\n"
|
"POT-Creation-Date: 2026-09-28 22:51+0200\n"
|
||||||
"PO-Revision-Date: 2026-09-23 12:00+0200\n"
|
"PO-Revision-Date: 2026-09-23 12:00+0200\n"
|
||||||
"Last-Translator: Proton Drive for Linux contributors\n"
|
"Last-Translator: Proton Drive for Linux contributors\n"
|
||||||
"Language-Team: German\n"
|
"Language-Team: German\n"
|
||||||
@@ -57,7 +57,7 @@ msgstr "%-d. %b %Y"
|
|||||||
msgid "%A, %B %-d, %Y, %H:%M"
|
msgid "%A, %B %-d, %Y, %H:%M"
|
||||||
msgstr "%A, %-d. %B %Y, %H:%M"
|
msgstr "%A, %-d. %B %Y, %H:%M"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:882
|
#: crates/pdfs-gui/src/app/main.rs:734
|
||||||
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
||||||
@@ -68,91 +68,91 @@ msgstr "%A, %-d. %B %Y, %H:%M"
|
|||||||
msgid "My files"
|
msgid "My files"
|
||||||
msgstr "Meine Dateien"
|
msgstr "Meine Dateien"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:885 crates/pdfs-gui/src/app/main.rs:1529
|
#: crates/pdfs-gui/src/app/main.rs:737 crates/pdfs-gui/src/app/main.rs:1381
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
||||||
msgid "Photos"
|
msgid "Photos"
|
||||||
msgstr "Fotos"
|
msgstr "Fotos"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:890
|
#: crates/pdfs-gui/src/app/main.rs:742
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
||||||
msgid "Shared with me"
|
msgid "Shared with me"
|
||||||
msgstr "Mit mir geteilt"
|
msgstr "Mit mir geteilt"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:895
|
#: crates/pdfs-gui/src/app/main.rs:747
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
||||||
msgid "Shared by me"
|
msgid "Shared by me"
|
||||||
msgstr "Von mir geteilt"
|
msgstr "Von mir geteilt"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:898
|
#: crates/pdfs-gui/src/app/main.rs:750
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
||||||
msgid "Computers"
|
msgid "Computers"
|
||||||
msgstr "Computer"
|
msgstr "Computer"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:899
|
#: crates/pdfs-gui/src/app/main.rs:751
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
||||||
msgid "Trash"
|
msgid "Trash"
|
||||||
msgstr "Papierkorb"
|
msgstr "Papierkorb"
|
||||||
|
|
||||||
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:901
|
#: crates/pdfs-gui/src/app/main.rs:753
|
||||||
msgid "Sync"
|
msgid "Sync"
|
||||||
msgstr "Synchronisierung"
|
msgstr "Synchronisierung"
|
||||||
|
|
||||||
#. Translators: a failed action's toast. {what} says what failed, such as
|
#. Translators: a failed action's toast. {what} says what failed, such as
|
||||||
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
||||||
#. from the mount service.
|
#. from the mount service.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1218
|
#: crates/pdfs-gui/src/app/main.rs:1070
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{what}: {detail}"
|
msgid "{what}: {detail}"
|
||||||
msgstr "{what}: {detail}"
|
msgstr "{what}: {detail}"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1237
|
#: crates/pdfs-gui/src/app/main.rs:1089
|
||||||
msgid "You're offline"
|
msgid "You're offline"
|
||||||
msgstr "Keine Internetverbindung"
|
msgstr "Keine Internetverbindung"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1238
|
#: crates/pdfs-gui/src/app/main.rs:1090
|
||||||
msgid "That's not there any more"
|
msgid "That's not there any more"
|
||||||
msgstr "Das ist nicht mehr vorhanden"
|
msgstr "Das ist nicht mehr vorhanden"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1239
|
#: crates/pdfs-gui/src/app/main.rs:1091
|
||||||
msgid "You don't have access to that"
|
msgid "You don't have access to that"
|
||||||
msgstr "Kein Zugriff darauf"
|
msgstr "Kein Zugriff darauf"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1240
|
#: crates/pdfs-gui/src/app/main.rs:1092
|
||||||
msgid "Something changed this first"
|
msgid "Something changed this first"
|
||||||
msgstr "Etwas anderes hat dies zuerst geändert"
|
msgstr "Etwas anderes hat dies zuerst geändert"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1241
|
#: crates/pdfs-gui/src/app/main.rs:1093
|
||||||
msgid "Your Proton Drive is full"
|
msgid "Your Proton Drive is full"
|
||||||
msgstr "Das Proton Drive ist voll"
|
msgstr "Das Proton Drive ist voll"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1282 crates/pdfs-gui/src/app/main.rs:1511
|
#: crates/pdfs-gui/src/app/main.rs:1134 crates/pdfs-gui/src/app/main.rs:1363
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
||||||
msgid "Preferences"
|
msgid "Preferences"
|
||||||
msgstr "Einstellungen"
|
msgstr "Einstellungen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1283
|
#: crates/pdfs-gui/src/app/main.rs:1135
|
||||||
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
||||||
msgid "Keyboard Shortcuts"
|
msgid "Keyboard Shortcuts"
|
||||||
msgstr "Tastenkürzel"
|
msgstr "Tastenkürzel"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1285
|
#: crates/pdfs-gui/src/app/main.rs:1137
|
||||||
msgid "About Proton Drive for Linux"
|
msgid "About Proton Drive for Linux"
|
||||||
msgstr "Info zu Proton Drive for Linux"
|
msgstr "Info zu Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1290
|
#: crates/pdfs-gui/src/app/main.rs:1142
|
||||||
msgid "Main menu"
|
msgid "Main menu"
|
||||||
msgstr "Hauptmenü"
|
msgstr "Hauptmenü"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1346
|
#: crates/pdfs-gui/src/app/main.rs:1198
|
||||||
msgid "Proton Drive for Linux"
|
msgid "Proton Drive for Linux"
|
||||||
msgstr "Proton Drive for Linux"
|
msgstr "Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1354
|
#: crates/pdfs-gui/src/app/main.rs:1206
|
||||||
msgid ""
|
msgid ""
|
||||||
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -164,23 +164,23 @@ msgstr ""
|
|||||||
"Inoffizieller Client – nicht mit Proton AG verbunden, von Proton AG "
|
"Inoffizieller Client – nicht mit Proton AG verbunden, von Proton AG "
|
||||||
"unterstützt oder befürwortet."
|
"unterstützt oder befürwortet."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1359
|
#: crates/pdfs-gui/src/app/main.rs:1211
|
||||||
msgid "Source Code"
|
msgid "Source Code"
|
||||||
msgstr "Quellcode"
|
msgstr "Quellcode"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1363
|
#: crates/pdfs-gui/src/app/main.rs:1215
|
||||||
msgid "Proton Terms of Service"
|
msgid "Proton Terms of Service"
|
||||||
msgstr "Nutzungsbedingungen von Proton"
|
msgstr "Nutzungsbedingungen von Proton"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1367
|
#: crates/pdfs-gui/src/app/main.rs:1219
|
||||||
msgid "Proton Privacy Policy"
|
msgid "Proton Privacy Policy"
|
||||||
msgstr "Datenschutzerklärung von Proton"
|
msgstr "Datenschutzerklärung von Proton"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1372
|
#: crates/pdfs-gui/src/app/main.rs:1224
|
||||||
msgid "Disclaimer"
|
msgid "Disclaimer"
|
||||||
msgstr "Haftungsausschluss"
|
msgstr "Haftungsausschluss"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1376
|
#: crates/pdfs-gui/src/app/main.rs:1228
|
||||||
msgid ""
|
msgid ""
|
||||||
"This is an unofficial, community-built client. It is not affiliated with, "
|
"This is an unofficial, community-built client. It is not affiliated with, "
|
||||||
"endorsed by, or supported by Proton AG.\n"
|
"endorsed by, or supported by Proton AG.\n"
|
||||||
@@ -205,7 +205,7 @@ msgstr ""
|
|||||||
"bereitgestellt. Daten, deren Verlust nicht hinnehmbar ist, sollten "
|
"bereitgestellt. Daten, deren Verlust nicht hinnehmbar ist, sollten "
|
||||||
"zusätzlich gesichert werden."
|
"zusätzlich gesichert werden."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1384
|
#: crates/pdfs-gui/src/app/main.rs:1236
|
||||||
msgid ""
|
msgid ""
|
||||||
"Places are named with data from <a href=\"https://www.geonames.org/"
|
"Places are named with data from <a href=\"https://www.geonames.org/"
|
||||||
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
||||||
@@ -215,7 +215,7 @@ msgstr ""
|
|||||||
"\">GeoNames</a>, lizenziert unter <a href=\"https://creativecommons.org/"
|
"\">GeoNames</a>, lizenziert unter <a href=\"https://creativecommons.org/"
|
||||||
"licenses/by/4.0/\">CC BY 4.0</a>."
|
"licenses/by/4.0/\">CC BY 4.0</a>."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1392
|
#: crates/pdfs-gui/src/app/main.rs:1244
|
||||||
msgid ""
|
msgid ""
|
||||||
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
||||||
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
||||||
@@ -225,7 +225,7 @@ msgstr ""
|
|||||||
"href=\"https://www.naturalearthdata.com/\">Natural Earth</a> gezeichnet, die "
|
"href=\"https://www.naturalearthdata.com/\">Natural Earth</a> gezeichnet, die "
|
||||||
"gemeinfrei sind."
|
"gemeinfrei sind."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1400
|
#: crates/pdfs-gui/src/app/main.rs:1252
|
||||||
msgid ""
|
msgid ""
|
||||||
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
||||||
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
||||||
@@ -241,15 +241,15 @@ msgstr ""
|
|||||||
|
|
||||||
#. Translators: replace with your name(s) and email address(es), one
|
#. Translators: replace with your name(s) and email address(es), one
|
||||||
#. per line, to be credited in the About dialog.
|
#. per line, to be credited in the About dialog.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1411
|
#: crates/pdfs-gui/src/app/main.rs:1263
|
||||||
msgid "translator-credits"
|
msgid "translator-credits"
|
||||||
msgstr "Mitwirkende an Proton Drive for Linux"
|
msgstr "Mitwirkende an Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1449
|
#: crates/pdfs-gui/src/app/main.rs:1301
|
||||||
msgid "Stop Proton Drive?"
|
msgid "Stop Proton Drive?"
|
||||||
msgstr "Proton Drive beenden?"
|
msgstr "Proton Drive beenden?"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1450
|
#: crates/pdfs-gui/src/app/main.rs:1302
|
||||||
msgid ""
|
msgid ""
|
||||||
"My files goes offline and nothing syncs until you connect again. Proton "
|
"My files goes offline and nothing syncs until you connect again. Proton "
|
||||||
"Drive starts again at your next login."
|
"Drive starts again at your next login."
|
||||||
@@ -258,7 +258,7 @@ msgstr ""
|
|||||||
"eine Verbindung hergestellt wird. Proton Drive startet bei der nächsten "
|
"eine Verbindung hergestellt wird. Proton Drive startet bei der nächsten "
|
||||||
"Anmeldung erneut."
|
"Anmeldung erneut."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1452 crates/pdfs-gui/src/app/main.rs:1727
|
#: crates/pdfs-gui/src/app/main.rs:1304 crates/pdfs-gui/src/app/main.rs:1579
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
||||||
@@ -287,30 +287,30 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Abbrechen"
|
msgstr "Abbrechen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1453
|
#: crates/pdfs-gui/src/app/main.rs:1305
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Stop"
|
msgid "Stop"
|
||||||
msgstr "Beenden"
|
msgstr "Beenden"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1470
|
#: crates/pdfs-gui/src/app/main.rs:1322
|
||||||
msgid "Couldn't stop Proton Drive"
|
msgid "Couldn't stop Proton Drive"
|
||||||
msgstr "Proton Drive konnte nicht beendet werden"
|
msgstr "Proton Drive konnte nicht beendet werden"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1471
|
#: crates/pdfs-gui/src/app/main.rs:1323
|
||||||
msgid "The Proton Drive service did not stop."
|
msgid "The Proton Drive service did not stop."
|
||||||
msgstr "Der Proton-Drive-Dienst wurde nicht beendet."
|
msgstr "Der Proton-Drive-Dienst wurde nicht beendet."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1507
|
#: crates/pdfs-gui/src/app/main.rs:1359
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
||||||
msgid "General"
|
msgid "General"
|
||||||
msgstr "Allgemein"
|
msgstr "Allgemein"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1509
|
#: crates/pdfs-gui/src/app/main.rs:1361
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
||||||
msgid "Search Drive"
|
msgid "Search Drive"
|
||||||
msgstr "Drive durchsuchen"
|
msgstr "Drive durchsuchen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1510
|
#: crates/pdfs-gui/src/app/main.rs:1362
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
||||||
@@ -319,38 +319,38 @@ msgstr "Drive durchsuchen"
|
|||||||
msgid "Refresh"
|
msgid "Refresh"
|
||||||
msgstr "Aktualisieren"
|
msgstr "Aktualisieren"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1512
|
#: crates/pdfs-gui/src/app/main.rs:1364
|
||||||
msgid "Keyboard shortcuts"
|
msgid "Keyboard shortcuts"
|
||||||
msgstr "Tastenkürzel"
|
msgstr "Tastenkürzel"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1516
|
#: crates/pdfs-gui/src/app/main.rs:1368
|
||||||
msgid "Files"
|
msgid "Files"
|
||||||
msgstr "Dateien"
|
msgstr "Dateien"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1518
|
#: crates/pdfs-gui/src/app/main.rs:1370
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
||||||
msgid "New folder"
|
msgid "New folder"
|
||||||
msgstr "Neuer Ordner"
|
msgstr "Neuer Ordner"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1519
|
#: crates/pdfs-gui/src/app/main.rs:1371
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
||||||
msgid "Upload files"
|
msgid "Upload files"
|
||||||
msgstr "Dateien hochladen"
|
msgstr "Dateien hochladen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1520
|
#: crates/pdfs-gui/src/app/main.rs:1372
|
||||||
msgid "Back / forward"
|
msgid "Back / forward"
|
||||||
msgstr "Zurück / Vor"
|
msgstr "Zurück / Vor"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1521
|
#: crates/pdfs-gui/src/app/main.rs:1373
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
||||||
msgid "Parent folder"
|
msgid "Parent folder"
|
||||||
msgstr "Übergeordneter Ordner"
|
msgstr "Übergeordneter Ordner"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1522
|
#: crates/pdfs-gui/src/app/main.rs:1374
|
||||||
msgid "Grid / list"
|
msgid "Grid / list"
|
||||||
msgstr "Raster / Liste"
|
msgstr "Raster / Liste"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1523
|
#: crates/pdfs-gui/src/app/main.rs:1375
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
||||||
@@ -359,7 +359,7 @@ msgstr "Raster / Liste"
|
|||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
msgstr "Umbenennen"
|
msgstr "Umbenennen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1524 crates/pdfs-gui/src/app/main.rs:1544
|
#: crates/pdfs-gui/src/app/main.rs:1376 crates/pdfs-gui/src/app/main.rs:1396
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
||||||
@@ -369,72 +369,73 @@ msgstr "Umbenennen"
|
|||||||
msgid "Move to Trash"
|
msgid "Move to Trash"
|
||||||
msgstr "In den Papierkorb verschieben"
|
msgstr "In den Papierkorb verschieben"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1525
|
#: crates/pdfs-gui/src/app/main.rs:1377
|
||||||
msgid "Clear the selection"
|
msgid "Clear the selection"
|
||||||
msgstr "Auswahl aufheben"
|
msgstr "Auswahl aufheben"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1531
|
#: crates/pdfs-gui/src/app/main.rs:1383
|
||||||
msgid "Larger thumbnails"
|
msgid "Larger thumbnails"
|
||||||
msgstr "Größere Vorschaubilder"
|
msgstr "Größere Vorschaubilder"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1532
|
#: crates/pdfs-gui/src/app/main.rs:1384
|
||||||
msgid "Smaller thumbnails"
|
msgid "Smaller thumbnails"
|
||||||
msgstr "Kleinere Vorschaubilder"
|
msgstr "Kleinere Vorschaubilder"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1533
|
#: crates/pdfs-gui/src/app/main.rs:1385
|
||||||
msgid "Reset thumbnail size"
|
msgid "Reset thumbnail size"
|
||||||
msgstr "Vorschaubildgröße zurücksetzen"
|
msgstr "Vorschaubildgröße zurücksetzen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1534
|
#: crates/pdfs-gui/src/app/main.rs:1386
|
||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Alles auswählen"
|
msgstr "Alles auswählen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1538
|
#: crates/pdfs-gui/src/app/main.rs:1390
|
||||||
msgid "Photo Viewer"
|
msgid "Photo Viewer"
|
||||||
msgstr "Fotobetrachter"
|
msgstr "Fotobetrachter"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1540
|
#: crates/pdfs-gui/src/app/main.rs:1392
|
||||||
msgid "Previous / next photo"
|
msgid "Previous / next photo"
|
||||||
msgstr "Vorheriges / nächstes Foto"
|
msgstr "Vorheriges / nächstes Foto"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1541
|
#: crates/pdfs-gui/src/app/main.rs:1393
|
||||||
msgid "First / last photo"
|
msgid "First / last photo"
|
||||||
msgstr "Erstes / letztes Foto"
|
msgstr "Erstes / letztes Foto"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1542
|
#: crates/pdfs-gui/src/app/main.rs:1394
|
||||||
msgid "Show details"
|
msgid "Show details"
|
||||||
msgstr "Details anzeigen"
|
msgstr "Details anzeigen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1543
|
#: crates/pdfs-gui/src/app/main.rs:1395
|
||||||
msgid "Fullscreen"
|
msgid "Fullscreen"
|
||||||
msgstr "Vollbild"
|
msgstr "Vollbild"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1545
|
#: crates/pdfs-gui/src/app/main.rs:1397
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "Schließen"
|
msgstr "Schließen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1635
|
#: crates/pdfs-gui/src/app/main.rs:1487
|
||||||
msgid "Select a single item to rename it"
|
msgid "Select a single item to rename it"
|
||||||
msgstr "Zum Umbenennen genau ein Element auswählen"
|
msgstr "Zum Umbenennen genau ein Element auswählen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1795
|
#: crates/pdfs-gui/src/app/main.rs:1647
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
||||||
msgid "Not connected"
|
msgid "Not connected"
|
||||||
msgstr "Nicht verbunden"
|
msgstr "Nicht verbunden"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1796
|
#: crates/pdfs-gui/src/app/main.rs:1648
|
||||||
msgid "The Proton Drive service isn't running."
|
msgid "The Proton Drive service isn't running."
|
||||||
msgstr "Der Proton-Drive-Dienst läuft nicht."
|
msgstr "Der Proton-Drive-Dienst läuft nicht."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1804
|
#: crates/pdfs-gui/src/app/main.rs:1656
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:336 crates/pdfs-gui/src/main.rs:444
|
#: crates/pdfs-gui/src/app/pages/status.rs:336
|
||||||
#: crates/pdfs-gui/src/prompt.rs:616 crates/pdfs-gui/src/prompt.rs:867
|
#: crates/pdfs-gui/src/hud/mod.rs:274 crates/pdfs-gui/src/hud/mod.rs:589
|
||||||
|
#: crates/pdfs-gui/src/main.rs:444
|
||||||
msgid "Connecting…"
|
msgid "Connecting…"
|
||||||
msgstr "Verbindung wird hergestellt …"
|
msgstr "Verbindung wird hergestellt …"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1805
|
#: crates/pdfs-gui/src/app/main.rs:1657
|
||||||
msgid "Waiting for the Proton Drive service to start."
|
msgid "Waiting for the Proton Drive service to start."
|
||||||
msgstr "Warten auf den Start des Proton-Drive-Dienstes."
|
msgstr "Warten auf den Start des Proton-Drive-Dienstes."
|
||||||
|
|
||||||
@@ -479,7 +480,7 @@ msgstr "Nichts im aktuellen Protokoll entspricht diesem Filter."
|
|||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/prompt.rs:595
|
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/hud/mod.rs:245
|
||||||
msgid "Retry"
|
msgid "Retry"
|
||||||
msgstr "Erneut versuchen"
|
msgstr "Erneut versuchen"
|
||||||
|
|
||||||
@@ -1238,6 +1239,7 @@ msgstr "Der Proton-Drive-Dienst hat nicht geantwortet."
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:265
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
msgstr "Öffnen"
|
msgstr "Öffnen"
|
||||||
@@ -1428,6 +1430,7 @@ msgstr "Name"
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:133
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Size"
|
msgid "Size"
|
||||||
msgstr "Größe"
|
msgstr "Größe"
|
||||||
@@ -1606,6 +1609,7 @@ msgstr[1] "{n} Dateien sind jetzt nur online verfügbar"
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:134
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Modified"
|
msgid "Modified"
|
||||||
msgstr "Geändert"
|
msgstr "Geändert"
|
||||||
@@ -1614,6 +1618,7 @@ msgstr "Geändert"
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:135
|
||||||
msgid "Location"
|
msgid "Location"
|
||||||
msgstr "Ort"
|
msgstr "Ort"
|
||||||
|
|
||||||
@@ -1632,6 +1637,7 @@ msgstr "Abspielen"
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:296
|
||||||
msgid "Available offline"
|
msgid "Available offline"
|
||||||
msgstr "Offline verfügbar"
|
msgstr "Offline verfügbar"
|
||||||
|
|
||||||
@@ -2054,7 +2060,6 @@ msgstr "Proton Drive herunterladen"
|
|||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
||||||
#: crates/pdfs-gui/src/prompt.rs:568
|
|
||||||
msgid "This computer"
|
msgid "This computer"
|
||||||
msgstr "Dieser Computer"
|
msgstr "Dieser Computer"
|
||||||
|
|
||||||
@@ -2546,7 +2551,7 @@ msgid "About Sync Modes"
|
|||||||
msgstr "Über Synchronisierungsarten"
|
msgstr "Über Synchronisierungsarten"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
||||||
#: crates/pdfs-gui/src/prompt.rs:60
|
#: crates/pdfs-gui/src/prompt.rs:65
|
||||||
msgid "Folders"
|
msgid "Folders"
|
||||||
msgstr "Ordner"
|
msgstr "Ordner"
|
||||||
|
|
||||||
@@ -3423,7 +3428,7 @@ msgid "Select Photo to Upload"
|
|||||||
msgstr "Foto zum Hochladen auswählen"
|
msgstr "Foto zum Hochladen auswählen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
||||||
#: crates/pdfs-gui/src/prompt.rs:62
|
#: crates/pdfs-gui/src/prompt.rs:67
|
||||||
msgid "Images"
|
msgid "Images"
|
||||||
msgstr "Bilder"
|
msgstr "Bilder"
|
||||||
|
|
||||||
@@ -4602,7 +4607,7 @@ msgid "Up to date"
|
|||||||
msgstr "Auf dem neuesten Stand"
|
msgstr "Auf dem neuesten Stand"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
||||||
#: crates/pdfs-gui/src/prompt.rs:880 crates/pdfs-gui/src/prompt.rs:893
|
#: crates/pdfs-gui/src/hud/mod.rs:602 crates/pdfs-gui/src/hud/mod.rs:616
|
||||||
msgid "Proton Drive isn't running"
|
msgid "Proton Drive isn't running"
|
||||||
msgstr "Proton Drive läuft nicht"
|
msgstr "Proton Drive läuft nicht"
|
||||||
|
|
||||||
@@ -5147,6 +5152,7 @@ msgid "Proton Drive"
|
|||||||
msgstr "Proton Drive"
|
msgstr "Proton Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:263
|
||||||
msgid "Open folder"
|
msgid "Open folder"
|
||||||
msgstr "Ordner öffnen"
|
msgstr "Ordner öffnen"
|
||||||
|
|
||||||
@@ -5529,6 +5535,137 @@ msgstr "(keine angehefteten Elemente – zum Suchen tippen)"
|
|||||||
msgid "(cannot resolve app dirs)"
|
msgid "(cannot resolve app dirs)"
|
||||||
msgstr "(Anwendungsverzeichnisse nicht ermittelbar)"
|
msgstr "(Anwendungsverzeichnisse nicht ermittelbar)"
|
||||||
|
|
||||||
|
#. Translators: the launcher's section of recently opened files.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:57
|
||||||
|
msgid "Recent"
|
||||||
|
msgstr "Zuletzt verwendet"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:58
|
||||||
|
msgid "Pinned in Proton Drive"
|
||||||
|
msgstr "In Proton Drive angeheftet"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:59
|
||||||
|
msgid "Best matches"
|
||||||
|
msgstr "Beste Treffer"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:159
|
||||||
|
msgid "Search Proton Drive"
|
||||||
|
msgstr "Proton Drive durchsuchen"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:180
|
||||||
|
msgid "Search in Drive and on this computer"
|
||||||
|
msgstr "In Drive und auf diesem Computer suchen"
|
||||||
|
|
||||||
|
#. Translators: the Escape key, shown as a hint that it closes the window.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:191
|
||||||
|
msgid "Esc"
|
||||||
|
msgstr "Esc"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:232 crates/pdfs-gui/src/hud/mod.rs:862
|
||||||
|
msgid "No results"
|
||||||
|
msgstr "Keine Ergebnisse"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:240
|
||||||
|
msgid "Proton Drive is not running"
|
||||||
|
msgstr "Proton Drive läuft nicht"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:241
|
||||||
|
msgid "Start Proton Drive to search your files."
|
||||||
|
msgstr "Proton Drive starten, um deine Dateien zu durchsuchen."
|
||||||
|
|
||||||
|
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:282
|
||||||
|
msgid "↑↓ navigate · ↵ open · Tab filter"
|
||||||
|
msgstr "↑↓ Navigieren · ↵ Öffnen · Tab Filter"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:864
|
||||||
|
msgid "Search your Drive"
|
||||||
|
msgstr "Das Drive durchsuchen"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:869
|
||||||
|
msgid "Still indexing this computer — local results will fill in shortly."
|
||||||
|
msgstr ""
|
||||||
|
"Dieser Computer wird noch indiziert – lokale Ergebnisse erscheinen in Kürze."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:871
|
||||||
|
msgid "Try a different search, or another filter."
|
||||||
|
msgstr "Eine andere Suche oder einen anderen Filter versuchen."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:873
|
||||||
|
msgid "Start typing to search Proton Drive and the files on this computer."
|
||||||
|
msgstr ""
|
||||||
|
"Mit dem Tippen beginnen, um Proton Drive und die Dateien auf diesem Computer "
|
||||||
|
"zu durchsuchen."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:900
|
||||||
|
msgid "No pinned files"
|
||||||
|
msgstr "Keine angehefteten Dateien"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:901
|
||||||
|
#, rust-format
|
||||||
|
msgid "{n} pinned file"
|
||||||
|
msgid_plural "{n} pinned files"
|
||||||
|
msgstr[0] "{n} angeheftete Datei"
|
||||||
|
msgstr[1] "{n} angeheftete Dateien"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:914
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
||||||
|
msgstr "{drive} in Drive · {local} auf diesem Computer · wird indiziert …"
|
||||||
|
|
||||||
|
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:919
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer"
|
||||||
|
msgstr "{drive} in Drive · {local} auf diesem Computer"
|
||||||
|
|
||||||
|
#. Translators: {name} is a file name.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1050
|
||||||
|
#, rust-format
|
||||||
|
msgid "Opening {name}…"
|
||||||
|
msgstr "{name} wird geöffnet …"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1090
|
||||||
|
#, rust-format
|
||||||
|
msgid "Could not open: {message}"
|
||||||
|
msgstr "Öffnen fehlgeschlagen: {message}"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1094
|
||||||
|
msgid "Couldn't reach Proton Drive"
|
||||||
|
msgstr "Proton Drive ist nicht erreichbar"
|
||||||
|
|
||||||
|
#. Translators: a heading in the launcher's preview pane, followed by
|
||||||
|
#. "Available offline", "Downloaded", "Online only" or "On this computer".
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:138
|
||||||
|
msgid "Availability"
|
||||||
|
msgstr "Verfügbarkeit"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:157
|
||||||
|
msgid "Show in Folder"
|
||||||
|
msgstr "Im Ordner anzeigen"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:160
|
||||||
|
msgid "Copy Path"
|
||||||
|
msgstr "Pfad kopieren"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:166
|
||||||
|
msgid "Path copied"
|
||||||
|
msgstr "Pfad kopiert"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:291
|
||||||
|
msgid "On this computer"
|
||||||
|
msgstr "Auf diesem Computer"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:303
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Downloaded"
|
||||||
|
msgstr "Heruntergeladen"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:308
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Online only"
|
||||||
|
msgstr "Nur online"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/i18n.rs:84
|
#: crates/pdfs-gui/src/i18n.rs:84
|
||||||
msgid "Kosovo"
|
msgid "Kosovo"
|
||||||
msgstr "Kosovo"
|
msgstr "Kosovo"
|
||||||
@@ -5682,16 +5819,16 @@ msgid "Quit"
|
|||||||
msgstr "Beenden"
|
msgstr "Beenden"
|
||||||
|
|
||||||
#. Translators: a filter chip that shows every kind of result.
|
#. Translators: a filter chip that shows every kind of result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:59
|
#: crates/pdfs-gui/src/prompt.rs:64
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Alle"
|
msgstr "Alle"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:61
|
#: crates/pdfs-gui/src/prompt.rs:66
|
||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Dokumente"
|
msgstr "Dokumente"
|
||||||
|
|
||||||
#. Translators: a filter chip for audio and video files.
|
#. Translators: a filter chip for audio and video files.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:64
|
#: crates/pdfs-gui/src/prompt.rs:69
|
||||||
msgid "Media"
|
msgid "Media"
|
||||||
msgstr "Medien"
|
msgstr "Medien"
|
||||||
|
|
||||||
@@ -5700,143 +5837,49 @@ msgstr "Medien"
|
|||||||
msgid "My files / {path}"
|
msgid "My files / {path}"
|
||||||
msgstr "Meine Dateien / {path}"
|
msgstr "Meine Dateien / {path}"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:520
|
|
||||||
msgid "Search Proton Drive"
|
|
||||||
msgstr "Proton Drive durchsuchen"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:541
|
|
||||||
msgid "Search in Drive and on this computer"
|
|
||||||
msgstr "In Drive und auf diesem Computer suchen"
|
|
||||||
|
|
||||||
#. Translators: the Escape key, shown as a hint that it closes the window.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:552
|
|
||||||
msgid "Esc"
|
|
||||||
msgstr "Esc"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:566 crates/pdfs-gui/src/prompt.rs:1085
|
|
||||||
msgid "Best matches"
|
|
||||||
msgstr "Beste Treffer"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:582 crates/pdfs-gui/src/prompt.rs:1109
|
|
||||||
msgid "No results"
|
|
||||||
msgstr "Keine Ergebnisse"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:590
|
|
||||||
msgid "Proton Drive is not running"
|
|
||||||
msgstr "Proton Drive läuft nicht"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:591
|
|
||||||
msgid "Start Proton Drive to search your files."
|
|
||||||
msgstr "Proton Drive starten, um deine Dateien zu durchsuchen."
|
|
||||||
|
|
||||||
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:624
|
|
||||||
msgid "↑↓ navigate · ↵ open · Tab filter"
|
|
||||||
msgstr "↑↓ Navigieren · ↵ Öffnen · Tab Filter"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1096
|
|
||||||
msgid "Pinned in Proton Drive"
|
|
||||||
msgstr "In Proton Drive angeheftet"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1111
|
|
||||||
msgid "Search your Drive"
|
|
||||||
msgstr "Das Drive durchsuchen"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1116
|
|
||||||
msgid "Still indexing this computer — local results will fill in shortly."
|
|
||||||
msgstr ""
|
|
||||||
"Dieser Computer wird noch indiziert – lokale Ergebnisse erscheinen in Kürze."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1118
|
|
||||||
msgid "Try a different search, or another filter."
|
|
||||||
msgstr "Eine andere Suche oder einen anderen Filter versuchen."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1120
|
|
||||||
msgid "Start typing to search Proton Drive and the files on this computer."
|
|
||||||
msgstr ""
|
|
||||||
"Mit dem Tippen beginnen, um Proton Drive und die Dateien auf diesem Computer "
|
|
||||||
"zu durchsuchen."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1146
|
|
||||||
msgid "No pinned files"
|
|
||||||
msgstr "Keine angehefteten Dateien"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1147
|
|
||||||
#, rust-format
|
|
||||||
msgid "{n} pinned file"
|
|
||||||
msgid_plural "{n} pinned files"
|
|
||||||
msgstr[0] "{n} angeheftete Datei"
|
|
||||||
msgstr[1] "{n} angeheftete Dateien"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1156
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
|
||||||
msgstr "{drive} in Drive · {local} auf diesem Computer · wird indiziert …"
|
|
||||||
|
|
||||||
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1161
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer"
|
|
||||||
msgstr "{drive} in Drive · {local} auf diesem Computer"
|
|
||||||
|
|
||||||
#. Translators: {name} is a file name.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1294
|
|
||||||
#, rust-format
|
|
||||||
msgid "Opening {name}…"
|
|
||||||
msgstr "{name} wird geöffnet …"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1317
|
|
||||||
#, rust-format
|
|
||||||
msgid "Could not open: {message}"
|
|
||||||
msgstr "Öffnen fehlgeschlagen: {message}"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1321
|
|
||||||
msgid "Couldn't reach Proton Drive"
|
|
||||||
msgstr "Proton Drive ist nicht erreichbar"
|
|
||||||
|
|
||||||
#. Translators: the user's home folder, as the location of a result.
|
#. Translators: the user's home folder, as the location of a result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1430
|
#: crates/pdfs-gui/src/prompt.rs:455
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Persönlicher Ordner"
|
msgstr "Persönlicher Ordner"
|
||||||
|
|
||||||
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1432
|
#: crates/pdfs-gui/src/prompt.rs:457
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "Home / {path}"
|
msgid "Home / {path}"
|
||||||
msgstr "Persönlicher Ordner / {path}"
|
msgstr "Persönlicher Ordner / {path}"
|
||||||
|
|
||||||
#. Translators: the age of a file modified under a minute ago.
|
#. Translators: the age of a file modified under a minute ago.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1452
|
#: crates/pdfs-gui/src/prompt.rs:477
|
||||||
msgctxt "age"
|
msgctxt "age"
|
||||||
msgid "now"
|
msgid "now"
|
||||||
msgstr "jetzt"
|
msgstr "jetzt"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of minutes.
|
#. Translators: a compact file age; {count} is a number of minutes.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1454
|
#: crates/pdfs-gui/src/prompt.rs:479
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}m"
|
msgid "{count}m"
|
||||||
msgstr "{count} Min."
|
msgstr "{count} Min."
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of hours.
|
#. Translators: a compact file age; {count} is a number of hours.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1456
|
#: crates/pdfs-gui/src/prompt.rs:481
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}h"
|
msgid "{count}h"
|
||||||
msgstr "{count} Std."
|
msgstr "{count} Std."
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of days.
|
#. Translators: a compact file age; {count} is a number of days.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1458
|
#: crates/pdfs-gui/src/prompt.rs:483
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}d"
|
msgid "{count}d"
|
||||||
msgstr "{count} T."
|
msgstr "{count} T."
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of months.
|
#. Translators: a compact file age; {count} is a number of months.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1460
|
#: crates/pdfs-gui/src/prompt.rs:485
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}mo"
|
msgid "{count}mo"
|
||||||
msgstr "{count} Mon."
|
msgstr "{count} Mon."
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of years.
|
#. Translators: a compact file age; {count} is a number of years.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1462
|
#: crates/pdfs-gui/src/prompt.rs:487
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}y"
|
msgid "{count}y"
|
||||||
msgstr "{count} J."
|
msgstr "{count} J."
|
||||||
@@ -5986,9 +6029,6 @@ msgstr "Proton-Drive-Hintergrunddienst nicht erreichbar: {error}"
|
|||||||
#~ msgid "Use this computer's identity"
|
#~ msgid "Use this computer's identity"
|
||||||
#~ msgstr "Identität dieses Computers verwenden"
|
#~ msgstr "Identität dieses Computers verwenden"
|
||||||
|
|
||||||
#~ msgid "On this computer"
|
|
||||||
#~ msgstr "Auf diesem Computer"
|
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
#~ "Where Proton Drive lives on this machine. Synced folders keep a full copy "
|
#~ "Where Proton Drive lives on this machine. Synced folders keep a full copy "
|
||||||
#~ "on this disk; on-demand folders keep the files in Proton Drive only and "
|
#~ "on this disk; on-demand folders keep the files in Proton Drive only and "
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: proton-drive-linux 2.1.0\n"
|
"Project-Id-Version: proton-drive-linux 2.1.0\n"
|
||||||
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
||||||
"POT-Creation-Date: 2026-09-28 16:17+0200\n"
|
"POT-Creation-Date: 2026-09-28 22:51+0200\n"
|
||||||
"PO-Revision-Date: 2026-09-23 12:00+0200\n"
|
"PO-Revision-Date: 2026-09-23 12:00+0200\n"
|
||||||
"Last-Translator: Proton Drive for Linux contributors\n"
|
"Last-Translator: Proton Drive for Linux contributors\n"
|
||||||
"Language-Team: English (United Kingdom)\n"
|
"Language-Team: English (United Kingdom)\n"
|
||||||
@@ -57,7 +57,7 @@ msgstr "%-d %b %Y"
|
|||||||
msgid "%A, %B %-d, %Y, %H:%M"
|
msgid "%A, %B %-d, %Y, %H:%M"
|
||||||
msgstr "%A %-d %B %Y, %H:%M"
|
msgstr "%A %-d %B %Y, %H:%M"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:882
|
#: crates/pdfs-gui/src/app/main.rs:734
|
||||||
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
||||||
@@ -68,91 +68,91 @@ msgstr "%A %-d %B %Y, %H:%M"
|
|||||||
msgid "My files"
|
msgid "My files"
|
||||||
msgstr "My files"
|
msgstr "My files"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:885 crates/pdfs-gui/src/app/main.rs:1529
|
#: crates/pdfs-gui/src/app/main.rs:737 crates/pdfs-gui/src/app/main.rs:1381
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
||||||
msgid "Photos"
|
msgid "Photos"
|
||||||
msgstr "Photos"
|
msgstr "Photos"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:890
|
#: crates/pdfs-gui/src/app/main.rs:742
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
||||||
msgid "Shared with me"
|
msgid "Shared with me"
|
||||||
msgstr "Shared with me"
|
msgstr "Shared with me"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:895
|
#: crates/pdfs-gui/src/app/main.rs:747
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
||||||
msgid "Shared by me"
|
msgid "Shared by me"
|
||||||
msgstr "Shared by me"
|
msgstr "Shared by me"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:898
|
#: crates/pdfs-gui/src/app/main.rs:750
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
||||||
msgid "Computers"
|
msgid "Computers"
|
||||||
msgstr "Computers"
|
msgstr "Computers"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:899
|
#: crates/pdfs-gui/src/app/main.rs:751
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
||||||
msgid "Trash"
|
msgid "Trash"
|
||||||
msgstr "Trash"
|
msgstr "Trash"
|
||||||
|
|
||||||
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:901
|
#: crates/pdfs-gui/src/app/main.rs:753
|
||||||
msgid "Sync"
|
msgid "Sync"
|
||||||
msgstr "Sync"
|
msgstr "Sync"
|
||||||
|
|
||||||
#. Translators: a failed action's toast. {what} says what failed, such as
|
#. Translators: a failed action's toast. {what} says what failed, such as
|
||||||
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
||||||
#. from the mount service.
|
#. from the mount service.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1218
|
#: crates/pdfs-gui/src/app/main.rs:1070
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{what}: {detail}"
|
msgid "{what}: {detail}"
|
||||||
msgstr "{what}: {detail}"
|
msgstr "{what}: {detail}"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1237
|
#: crates/pdfs-gui/src/app/main.rs:1089
|
||||||
msgid "You're offline"
|
msgid "You're offline"
|
||||||
msgstr "You're offline"
|
msgstr "You're offline"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1238
|
#: crates/pdfs-gui/src/app/main.rs:1090
|
||||||
msgid "That's not there any more"
|
msgid "That's not there any more"
|
||||||
msgstr "That's not there any more"
|
msgstr "That's not there any more"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1239
|
#: crates/pdfs-gui/src/app/main.rs:1091
|
||||||
msgid "You don't have access to that"
|
msgid "You don't have access to that"
|
||||||
msgstr "You don't have access to that"
|
msgstr "You don't have access to that"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1240
|
#: crates/pdfs-gui/src/app/main.rs:1092
|
||||||
msgid "Something changed this first"
|
msgid "Something changed this first"
|
||||||
msgstr "Something changed this first"
|
msgstr "Something changed this first"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1241
|
#: crates/pdfs-gui/src/app/main.rs:1093
|
||||||
msgid "Your Proton Drive is full"
|
msgid "Your Proton Drive is full"
|
||||||
msgstr "Your Proton Drive is full"
|
msgstr "Your Proton Drive is full"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1282 crates/pdfs-gui/src/app/main.rs:1511
|
#: crates/pdfs-gui/src/app/main.rs:1134 crates/pdfs-gui/src/app/main.rs:1363
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
||||||
msgid "Preferences"
|
msgid "Preferences"
|
||||||
msgstr "Preferences"
|
msgstr "Preferences"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1283
|
#: crates/pdfs-gui/src/app/main.rs:1135
|
||||||
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
||||||
msgid "Keyboard Shortcuts"
|
msgid "Keyboard Shortcuts"
|
||||||
msgstr "Keyboard Shortcuts"
|
msgstr "Keyboard Shortcuts"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1285
|
#: crates/pdfs-gui/src/app/main.rs:1137
|
||||||
msgid "About Proton Drive for Linux"
|
msgid "About Proton Drive for Linux"
|
||||||
msgstr "About Proton Drive for Linux"
|
msgstr "About Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1290
|
#: crates/pdfs-gui/src/app/main.rs:1142
|
||||||
msgid "Main menu"
|
msgid "Main menu"
|
||||||
msgstr "Main menu"
|
msgstr "Main menu"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1346
|
#: crates/pdfs-gui/src/app/main.rs:1198
|
||||||
msgid "Proton Drive for Linux"
|
msgid "Proton Drive for Linux"
|
||||||
msgstr "Proton Drive for Linux"
|
msgstr "Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1354
|
#: crates/pdfs-gui/src/app/main.rs:1206
|
||||||
msgid ""
|
msgid ""
|
||||||
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -164,23 +164,23 @@ msgstr ""
|
|||||||
"Unofficial client — not affiliated with, endorsed by, or supported by Proton "
|
"Unofficial client — not affiliated with, endorsed by, or supported by Proton "
|
||||||
"AG."
|
"AG."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1359
|
#: crates/pdfs-gui/src/app/main.rs:1211
|
||||||
msgid "Source Code"
|
msgid "Source Code"
|
||||||
msgstr "Source Code"
|
msgstr "Source Code"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1363
|
#: crates/pdfs-gui/src/app/main.rs:1215
|
||||||
msgid "Proton Terms of Service"
|
msgid "Proton Terms of Service"
|
||||||
msgstr "Proton Terms of Service"
|
msgstr "Proton Terms of Service"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1367
|
#: crates/pdfs-gui/src/app/main.rs:1219
|
||||||
msgid "Proton Privacy Policy"
|
msgid "Proton Privacy Policy"
|
||||||
msgstr "Proton Privacy Policy"
|
msgstr "Proton Privacy Policy"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1372
|
#: crates/pdfs-gui/src/app/main.rs:1224
|
||||||
msgid "Disclaimer"
|
msgid "Disclaimer"
|
||||||
msgstr "Disclaimer"
|
msgstr "Disclaimer"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1376
|
#: crates/pdfs-gui/src/app/main.rs:1228
|
||||||
msgid ""
|
msgid ""
|
||||||
"This is an unofficial, community-built client. It is not affiliated with, "
|
"This is an unofficial, community-built client. It is not affiliated with, "
|
||||||
"endorsed by, or supported by Proton AG.\n"
|
"endorsed by, or supported by Proton AG.\n"
|
||||||
@@ -202,7 +202,7 @@ msgstr ""
|
|||||||
"This software is provided “as is”, without warranty of any kind. Keep a copy "
|
"This software is provided “as is”, without warranty of any kind. Keep a copy "
|
||||||
"of any data you cannot afford to lose."
|
"of any data you cannot afford to lose."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1384
|
#: crates/pdfs-gui/src/app/main.rs:1236
|
||||||
msgid ""
|
msgid ""
|
||||||
"Places are named with data from <a href=\"https://www.geonames.org/"
|
"Places are named with data from <a href=\"https://www.geonames.org/"
|
||||||
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
||||||
@@ -212,7 +212,7 @@ msgstr ""
|
|||||||
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
||||||
"licenses/by/4.0/\">CC BY 4.0</a>."
|
"licenses/by/4.0/\">CC BY 4.0</a>."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1392
|
#: crates/pdfs-gui/src/app/main.rs:1244
|
||||||
msgid ""
|
msgid ""
|
||||||
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
||||||
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
||||||
@@ -222,7 +222,7 @@ msgstr ""
|
|||||||
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
||||||
"public domain."
|
"public domain."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1400
|
#: crates/pdfs-gui/src/app/main.rs:1252
|
||||||
msgid ""
|
msgid ""
|
||||||
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
||||||
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
||||||
@@ -238,15 +238,15 @@ msgstr ""
|
|||||||
|
|
||||||
#. Translators: replace with your name(s) and email address(es), one
|
#. Translators: replace with your name(s) and email address(es), one
|
||||||
#. per line, to be credited in the About dialog.
|
#. per line, to be credited in the About dialog.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1411
|
#: crates/pdfs-gui/src/app/main.rs:1263
|
||||||
msgid "translator-credits"
|
msgid "translator-credits"
|
||||||
msgstr "Proton Drive for Linux contributors"
|
msgstr "Proton Drive for Linux contributors"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1449
|
#: crates/pdfs-gui/src/app/main.rs:1301
|
||||||
msgid "Stop Proton Drive?"
|
msgid "Stop Proton Drive?"
|
||||||
msgstr "Stop Proton Drive?"
|
msgstr "Stop Proton Drive?"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1450
|
#: crates/pdfs-gui/src/app/main.rs:1302
|
||||||
msgid ""
|
msgid ""
|
||||||
"My files goes offline and nothing syncs until you connect again. Proton "
|
"My files goes offline and nothing syncs until you connect again. Proton "
|
||||||
"Drive starts again at your next login."
|
"Drive starts again at your next login."
|
||||||
@@ -254,7 +254,7 @@ msgstr ""
|
|||||||
"My files goes offline and nothing syncs until you connect again. Proton "
|
"My files goes offline and nothing syncs until you connect again. Proton "
|
||||||
"Drive starts again at your next login."
|
"Drive starts again at your next login."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1452 crates/pdfs-gui/src/app/main.rs:1727
|
#: crates/pdfs-gui/src/app/main.rs:1304 crates/pdfs-gui/src/app/main.rs:1579
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
||||||
@@ -283,30 +283,30 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Cancel"
|
msgstr "Cancel"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1453
|
#: crates/pdfs-gui/src/app/main.rs:1305
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Stop"
|
msgid "Stop"
|
||||||
msgstr "Stop"
|
msgstr "Stop"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1470
|
#: crates/pdfs-gui/src/app/main.rs:1322
|
||||||
msgid "Couldn't stop Proton Drive"
|
msgid "Couldn't stop Proton Drive"
|
||||||
msgstr "Couldn't stop Proton Drive"
|
msgstr "Couldn't stop Proton Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1471
|
#: crates/pdfs-gui/src/app/main.rs:1323
|
||||||
msgid "The Proton Drive service did not stop."
|
msgid "The Proton Drive service did not stop."
|
||||||
msgstr "The Proton Drive service did not stop."
|
msgstr "The Proton Drive service did not stop."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1507
|
#: crates/pdfs-gui/src/app/main.rs:1359
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
||||||
msgid "General"
|
msgid "General"
|
||||||
msgstr "General"
|
msgstr "General"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1509
|
#: crates/pdfs-gui/src/app/main.rs:1361
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
||||||
msgid "Search Drive"
|
msgid "Search Drive"
|
||||||
msgstr "Search Drive"
|
msgstr "Search Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1510
|
#: crates/pdfs-gui/src/app/main.rs:1362
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
||||||
@@ -315,38 +315,38 @@ msgstr "Search Drive"
|
|||||||
msgid "Refresh"
|
msgid "Refresh"
|
||||||
msgstr "Refresh"
|
msgstr "Refresh"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1512
|
#: crates/pdfs-gui/src/app/main.rs:1364
|
||||||
msgid "Keyboard shortcuts"
|
msgid "Keyboard shortcuts"
|
||||||
msgstr "Keyboard shortcuts"
|
msgstr "Keyboard shortcuts"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1516
|
#: crates/pdfs-gui/src/app/main.rs:1368
|
||||||
msgid "Files"
|
msgid "Files"
|
||||||
msgstr "Files"
|
msgstr "Files"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1518
|
#: crates/pdfs-gui/src/app/main.rs:1370
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
||||||
msgid "New folder"
|
msgid "New folder"
|
||||||
msgstr "New folder"
|
msgstr "New folder"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1519
|
#: crates/pdfs-gui/src/app/main.rs:1371
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
||||||
msgid "Upload files"
|
msgid "Upload files"
|
||||||
msgstr "Upload files"
|
msgstr "Upload files"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1520
|
#: crates/pdfs-gui/src/app/main.rs:1372
|
||||||
msgid "Back / forward"
|
msgid "Back / forward"
|
||||||
msgstr "Back / forward"
|
msgstr "Back / forward"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1521
|
#: crates/pdfs-gui/src/app/main.rs:1373
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
||||||
msgid "Parent folder"
|
msgid "Parent folder"
|
||||||
msgstr "Parent folder"
|
msgstr "Parent folder"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1522
|
#: crates/pdfs-gui/src/app/main.rs:1374
|
||||||
msgid "Grid / list"
|
msgid "Grid / list"
|
||||||
msgstr "Grid / list"
|
msgstr "Grid / list"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1523
|
#: crates/pdfs-gui/src/app/main.rs:1375
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
||||||
@@ -355,7 +355,7 @@ msgstr "Grid / list"
|
|||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
msgstr "Rename"
|
msgstr "Rename"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1524 crates/pdfs-gui/src/app/main.rs:1544
|
#: crates/pdfs-gui/src/app/main.rs:1376 crates/pdfs-gui/src/app/main.rs:1396
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
||||||
@@ -365,72 +365,73 @@ msgstr "Rename"
|
|||||||
msgid "Move to Trash"
|
msgid "Move to Trash"
|
||||||
msgstr "Move to Trash"
|
msgstr "Move to Trash"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1525
|
#: crates/pdfs-gui/src/app/main.rs:1377
|
||||||
msgid "Clear the selection"
|
msgid "Clear the selection"
|
||||||
msgstr "Clear the selection"
|
msgstr "Clear the selection"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1531
|
#: crates/pdfs-gui/src/app/main.rs:1383
|
||||||
msgid "Larger thumbnails"
|
msgid "Larger thumbnails"
|
||||||
msgstr "Larger thumbnails"
|
msgstr "Larger thumbnails"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1532
|
#: crates/pdfs-gui/src/app/main.rs:1384
|
||||||
msgid "Smaller thumbnails"
|
msgid "Smaller thumbnails"
|
||||||
msgstr "Smaller thumbnails"
|
msgstr "Smaller thumbnails"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1533
|
#: crates/pdfs-gui/src/app/main.rs:1385
|
||||||
msgid "Reset thumbnail size"
|
msgid "Reset thumbnail size"
|
||||||
msgstr "Reset thumbnail size"
|
msgstr "Reset thumbnail size"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1534
|
#: crates/pdfs-gui/src/app/main.rs:1386
|
||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Select all"
|
msgstr "Select all"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1538
|
#: crates/pdfs-gui/src/app/main.rs:1390
|
||||||
msgid "Photo Viewer"
|
msgid "Photo Viewer"
|
||||||
msgstr "Photo Viewer"
|
msgstr "Photo Viewer"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1540
|
#: crates/pdfs-gui/src/app/main.rs:1392
|
||||||
msgid "Previous / next photo"
|
msgid "Previous / next photo"
|
||||||
msgstr "Previous / next photo"
|
msgstr "Previous / next photo"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1541
|
#: crates/pdfs-gui/src/app/main.rs:1393
|
||||||
msgid "First / last photo"
|
msgid "First / last photo"
|
||||||
msgstr "First / last photo"
|
msgstr "First / last photo"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1542
|
#: crates/pdfs-gui/src/app/main.rs:1394
|
||||||
msgid "Show details"
|
msgid "Show details"
|
||||||
msgstr "Show details"
|
msgstr "Show details"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1543
|
#: crates/pdfs-gui/src/app/main.rs:1395
|
||||||
msgid "Fullscreen"
|
msgid "Fullscreen"
|
||||||
msgstr "Fullscreen"
|
msgstr "Fullscreen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1545
|
#: crates/pdfs-gui/src/app/main.rs:1397
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "Close"
|
msgstr "Close"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1635
|
#: crates/pdfs-gui/src/app/main.rs:1487
|
||||||
msgid "Select a single item to rename it"
|
msgid "Select a single item to rename it"
|
||||||
msgstr "Select a single item to rename it"
|
msgstr "Select a single item to rename it"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1795
|
#: crates/pdfs-gui/src/app/main.rs:1647
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
||||||
msgid "Not connected"
|
msgid "Not connected"
|
||||||
msgstr "Not connected"
|
msgstr "Not connected"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1796
|
#: crates/pdfs-gui/src/app/main.rs:1648
|
||||||
msgid "The Proton Drive service isn't running."
|
msgid "The Proton Drive service isn't running."
|
||||||
msgstr "The Proton Drive service isn't running."
|
msgstr "The Proton Drive service isn't running."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1804
|
#: crates/pdfs-gui/src/app/main.rs:1656
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:336 crates/pdfs-gui/src/main.rs:444
|
#: crates/pdfs-gui/src/app/pages/status.rs:336
|
||||||
#: crates/pdfs-gui/src/prompt.rs:616 crates/pdfs-gui/src/prompt.rs:867
|
#: crates/pdfs-gui/src/hud/mod.rs:274 crates/pdfs-gui/src/hud/mod.rs:589
|
||||||
|
#: crates/pdfs-gui/src/main.rs:444
|
||||||
msgid "Connecting…"
|
msgid "Connecting…"
|
||||||
msgstr "Connecting…"
|
msgstr "Connecting…"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1805
|
#: crates/pdfs-gui/src/app/main.rs:1657
|
||||||
msgid "Waiting for the Proton Drive service to start."
|
msgid "Waiting for the Proton Drive service to start."
|
||||||
msgstr "Waiting for the Proton Drive service to start."
|
msgstr "Waiting for the Proton Drive service to start."
|
||||||
|
|
||||||
@@ -475,7 +476,7 @@ msgstr "Nothing in the recent log fits this filter."
|
|||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/prompt.rs:595
|
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/hud/mod.rs:245
|
||||||
msgid "Retry"
|
msgid "Retry"
|
||||||
msgstr "Retry"
|
msgstr "Retry"
|
||||||
|
|
||||||
@@ -1231,6 +1232,7 @@ msgstr "The Proton Drive service didn't respond."
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:265
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
msgstr "Open"
|
msgstr "Open"
|
||||||
@@ -1421,6 +1423,7 @@ msgstr "Name"
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:133
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Size"
|
msgid "Size"
|
||||||
msgstr "Size"
|
msgstr "Size"
|
||||||
@@ -1595,6 +1598,7 @@ msgstr[1] "{n} files are now online only"
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:134
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Modified"
|
msgid "Modified"
|
||||||
msgstr "Modified"
|
msgstr "Modified"
|
||||||
@@ -1603,6 +1607,7 @@ msgstr "Modified"
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:135
|
||||||
msgid "Location"
|
msgid "Location"
|
||||||
msgstr "Location"
|
msgstr "Location"
|
||||||
|
|
||||||
@@ -1621,6 +1626,7 @@ msgstr "Play"
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:296
|
||||||
msgid "Available offline"
|
msgid "Available offline"
|
||||||
msgstr "Available offline"
|
msgstr "Available offline"
|
||||||
|
|
||||||
@@ -2036,7 +2042,6 @@ msgstr "Get Proton Drive"
|
|||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
||||||
#: crates/pdfs-gui/src/prompt.rs:568
|
|
||||||
msgid "This computer"
|
msgid "This computer"
|
||||||
msgstr "This computer"
|
msgstr "This computer"
|
||||||
|
|
||||||
@@ -2516,7 +2521,7 @@ msgid "About Sync Modes"
|
|||||||
msgstr "About Sync Modes"
|
msgstr "About Sync Modes"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
||||||
#: crates/pdfs-gui/src/prompt.rs:60
|
#: crates/pdfs-gui/src/prompt.rs:65
|
||||||
msgid "Folders"
|
msgid "Folders"
|
||||||
msgstr "Folders"
|
msgstr "Folders"
|
||||||
|
|
||||||
@@ -3376,7 +3381,7 @@ msgid "Select Photo to Upload"
|
|||||||
msgstr "Select Photo to Upload"
|
msgstr "Select Photo to Upload"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
||||||
#: crates/pdfs-gui/src/prompt.rs:62
|
#: crates/pdfs-gui/src/prompt.rs:67
|
||||||
msgid "Images"
|
msgid "Images"
|
||||||
msgstr "Images"
|
msgstr "Images"
|
||||||
|
|
||||||
@@ -4521,7 +4526,7 @@ msgid "Up to date"
|
|||||||
msgstr "Up to date"
|
msgstr "Up to date"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
||||||
#: crates/pdfs-gui/src/prompt.rs:880 crates/pdfs-gui/src/prompt.rs:893
|
#: crates/pdfs-gui/src/hud/mod.rs:602 crates/pdfs-gui/src/hud/mod.rs:616
|
||||||
msgid "Proton Drive isn't running"
|
msgid "Proton Drive isn't running"
|
||||||
msgstr "Proton Drive isn't running"
|
msgstr "Proton Drive isn't running"
|
||||||
|
|
||||||
@@ -5053,6 +5058,7 @@ msgid "Proton Drive"
|
|||||||
msgstr "Proton Drive"
|
msgstr "Proton Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:263
|
||||||
msgid "Open folder"
|
msgid "Open folder"
|
||||||
msgstr "Open folder"
|
msgstr "Open folder"
|
||||||
|
|
||||||
@@ -5431,6 +5437,134 @@ msgstr "(no pinned files — type to search)"
|
|||||||
msgid "(cannot resolve app dirs)"
|
msgid "(cannot resolve app dirs)"
|
||||||
msgstr "(cannot resolve app dirs)"
|
msgstr "(cannot resolve app dirs)"
|
||||||
|
|
||||||
|
#. Translators: the launcher's section of recently opened files.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:57
|
||||||
|
msgid "Recent"
|
||||||
|
msgstr "Recent"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:58
|
||||||
|
msgid "Pinned in Proton Drive"
|
||||||
|
msgstr "Pinned in Proton Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:59
|
||||||
|
msgid "Best matches"
|
||||||
|
msgstr "Best matches"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:159
|
||||||
|
msgid "Search Proton Drive"
|
||||||
|
msgstr "Search Proton Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:180
|
||||||
|
msgid "Search in Drive and on this computer"
|
||||||
|
msgstr "Search in Drive and on this computer"
|
||||||
|
|
||||||
|
#. Translators: the Escape key, shown as a hint that it closes the window.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:191
|
||||||
|
msgid "Esc"
|
||||||
|
msgstr "Esc"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:232 crates/pdfs-gui/src/hud/mod.rs:862
|
||||||
|
msgid "No results"
|
||||||
|
msgstr "No results"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:240
|
||||||
|
msgid "Proton Drive is not running"
|
||||||
|
msgstr "Proton Drive is not running"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:241
|
||||||
|
msgid "Start Proton Drive to search your files."
|
||||||
|
msgstr "Start Proton Drive to search your files."
|
||||||
|
|
||||||
|
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:282
|
||||||
|
msgid "↑↓ navigate · ↵ open · Tab filter"
|
||||||
|
msgstr "↑↓ navigate · ↵ open · Tab filter"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:864
|
||||||
|
msgid "Search your Drive"
|
||||||
|
msgstr "Search your Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:869
|
||||||
|
msgid "Still indexing this computer — local results will fill in shortly."
|
||||||
|
msgstr "Still indexing this computer — local results will fill in shortly."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:871
|
||||||
|
msgid "Try a different search, or another filter."
|
||||||
|
msgstr "Try a different search, or another filter."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:873
|
||||||
|
msgid "Start typing to search Proton Drive and the files on this computer."
|
||||||
|
msgstr "Start typing to search Proton Drive and the files on this computer."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:900
|
||||||
|
msgid "No pinned files"
|
||||||
|
msgstr "No pinned files"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:901
|
||||||
|
#, rust-format
|
||||||
|
msgid "{n} pinned file"
|
||||||
|
msgid_plural "{n} pinned files"
|
||||||
|
msgstr[0] "{n} pinned file"
|
||||||
|
msgstr[1] "{n} pinned files"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:914
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
||||||
|
msgstr "{drive} in Drive · {local} on this computer · indexing…"
|
||||||
|
|
||||||
|
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:919
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer"
|
||||||
|
msgstr "{drive} in Drive · {local} on this computer"
|
||||||
|
|
||||||
|
#. Translators: {name} is a file name.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1050
|
||||||
|
#, rust-format
|
||||||
|
msgid "Opening {name}…"
|
||||||
|
msgstr "Opening {name}…"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1090
|
||||||
|
#, rust-format
|
||||||
|
msgid "Could not open: {message}"
|
||||||
|
msgstr "Could not open: {message}"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1094
|
||||||
|
msgid "Couldn't reach Proton Drive"
|
||||||
|
msgstr "Couldn't reach Proton Drive"
|
||||||
|
|
||||||
|
#. Translators: a heading in the launcher's preview pane, followed by
|
||||||
|
#. "Available offline", "Downloaded", "Online only" or "On this computer".
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:138
|
||||||
|
msgid "Availability"
|
||||||
|
msgstr "Availability"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:157
|
||||||
|
msgid "Show in Folder"
|
||||||
|
msgstr "Show in Folder"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:160
|
||||||
|
msgid "Copy Path"
|
||||||
|
msgstr "Copy Path"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:166
|
||||||
|
msgid "Path copied"
|
||||||
|
msgstr "Path copied"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:291
|
||||||
|
msgid "On this computer"
|
||||||
|
msgstr "On this computer"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:303
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Downloaded"
|
||||||
|
msgstr "Downloaded"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:308
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Online only"
|
||||||
|
msgstr "Online only"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/i18n.rs:84
|
#: crates/pdfs-gui/src/i18n.rs:84
|
||||||
msgid "Kosovo"
|
msgid "Kosovo"
|
||||||
msgstr "Kosovo"
|
msgstr "Kosovo"
|
||||||
@@ -5584,16 +5718,16 @@ msgid "Quit"
|
|||||||
msgstr "Quit"
|
msgstr "Quit"
|
||||||
|
|
||||||
#. Translators: a filter chip that shows every kind of result.
|
#. Translators: a filter chip that shows every kind of result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:59
|
#: crates/pdfs-gui/src/prompt.rs:64
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "All"
|
msgstr "All"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:61
|
#: crates/pdfs-gui/src/prompt.rs:66
|
||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Documents"
|
msgstr "Documents"
|
||||||
|
|
||||||
#. Translators: a filter chip for audio and video files.
|
#. Translators: a filter chip for audio and video files.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:64
|
#: crates/pdfs-gui/src/prompt.rs:69
|
||||||
msgid "Media"
|
msgid "Media"
|
||||||
msgstr "Media"
|
msgstr "Media"
|
||||||
|
|
||||||
@@ -5602,140 +5736,49 @@ msgstr "Media"
|
|||||||
msgid "My files / {path}"
|
msgid "My files / {path}"
|
||||||
msgstr "My files / {path}"
|
msgstr "My files / {path}"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:520
|
|
||||||
msgid "Search Proton Drive"
|
|
||||||
msgstr "Search Proton Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:541
|
|
||||||
msgid "Search in Drive and on this computer"
|
|
||||||
msgstr "Search in Drive and on this computer"
|
|
||||||
|
|
||||||
#. Translators: the Escape key, shown as a hint that it closes the window.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:552
|
|
||||||
msgid "Esc"
|
|
||||||
msgstr "Esc"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:566 crates/pdfs-gui/src/prompt.rs:1085
|
|
||||||
msgid "Best matches"
|
|
||||||
msgstr "Best matches"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:582 crates/pdfs-gui/src/prompt.rs:1109
|
|
||||||
msgid "No results"
|
|
||||||
msgstr "No results"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:590
|
|
||||||
msgid "Proton Drive is not running"
|
|
||||||
msgstr "Proton Drive is not running"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:591
|
|
||||||
msgid "Start Proton Drive to search your files."
|
|
||||||
msgstr "Start Proton Drive to search your files."
|
|
||||||
|
|
||||||
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:624
|
|
||||||
msgid "↑↓ navigate · ↵ open · Tab filter"
|
|
||||||
msgstr "↑↓ navigate · ↵ open · Tab filter"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1096
|
|
||||||
msgid "Pinned in Proton Drive"
|
|
||||||
msgstr "Pinned in Proton Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1111
|
|
||||||
msgid "Search your Drive"
|
|
||||||
msgstr "Search your Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1116
|
|
||||||
msgid "Still indexing this computer — local results will fill in shortly."
|
|
||||||
msgstr "Still indexing this computer — local results will fill in shortly."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1118
|
|
||||||
msgid "Try a different search, or another filter."
|
|
||||||
msgstr "Try a different search, or another filter."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1120
|
|
||||||
msgid "Start typing to search Proton Drive and the files on this computer."
|
|
||||||
msgstr "Start typing to search Proton Drive and the files on this computer."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1146
|
|
||||||
msgid "No pinned files"
|
|
||||||
msgstr "No pinned files"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1147
|
|
||||||
#, rust-format
|
|
||||||
msgid "{n} pinned file"
|
|
||||||
msgid_plural "{n} pinned files"
|
|
||||||
msgstr[0] "{n} pinned file"
|
|
||||||
msgstr[1] "{n} pinned files"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1156
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
|
||||||
msgstr "{drive} in Drive · {local} on this computer · indexing…"
|
|
||||||
|
|
||||||
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1161
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer"
|
|
||||||
msgstr "{drive} in Drive · {local} on this computer"
|
|
||||||
|
|
||||||
#. Translators: {name} is a file name.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1294
|
|
||||||
#, rust-format
|
|
||||||
msgid "Opening {name}…"
|
|
||||||
msgstr "Opening {name}…"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1317
|
|
||||||
#, rust-format
|
|
||||||
msgid "Could not open: {message}"
|
|
||||||
msgstr "Could not open: {message}"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1321
|
|
||||||
msgid "Couldn't reach Proton Drive"
|
|
||||||
msgstr "Couldn't reach Proton Drive"
|
|
||||||
|
|
||||||
#. Translators: the user's home folder, as the location of a result.
|
#. Translators: the user's home folder, as the location of a result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1430
|
#: crates/pdfs-gui/src/prompt.rs:455
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Home"
|
msgstr "Home"
|
||||||
|
|
||||||
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1432
|
#: crates/pdfs-gui/src/prompt.rs:457
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "Home / {path}"
|
msgid "Home / {path}"
|
||||||
msgstr "Home / {path}"
|
msgstr "Home / {path}"
|
||||||
|
|
||||||
#. Translators: the age of a file modified under a minute ago.
|
#. Translators: the age of a file modified under a minute ago.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1452
|
#: crates/pdfs-gui/src/prompt.rs:477
|
||||||
msgctxt "age"
|
msgctxt "age"
|
||||||
msgid "now"
|
msgid "now"
|
||||||
msgstr "now"
|
msgstr "now"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of minutes.
|
#. Translators: a compact file age; {count} is a number of minutes.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1454
|
#: crates/pdfs-gui/src/prompt.rs:479
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}m"
|
msgid "{count}m"
|
||||||
msgstr "{count}m"
|
msgstr "{count}m"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of hours.
|
#. Translators: a compact file age; {count} is a number of hours.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1456
|
#: crates/pdfs-gui/src/prompt.rs:481
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}h"
|
msgid "{count}h"
|
||||||
msgstr "{count}h"
|
msgstr "{count}h"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of days.
|
#. Translators: a compact file age; {count} is a number of days.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1458
|
#: crates/pdfs-gui/src/prompt.rs:483
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}d"
|
msgid "{count}d"
|
||||||
msgstr "{count}d"
|
msgstr "{count}d"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of months.
|
#. Translators: a compact file age; {count} is a number of months.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1460
|
#: crates/pdfs-gui/src/prompt.rs:485
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}mo"
|
msgid "{count}mo"
|
||||||
msgstr "{count}mo"
|
msgstr "{count}mo"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of years.
|
#. Translators: a compact file age; {count} is a number of years.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1462
|
#: crates/pdfs-gui/src/prompt.rs:487
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}y"
|
msgid "{count}y"
|
||||||
msgstr "{count}y"
|
msgstr "{count}y"
|
||||||
@@ -5882,9 +5925,6 @@ msgstr "cannot reach the Proton Drive daemon: {error}"
|
|||||||
#~ msgid "Use this computer's identity"
|
#~ msgid "Use this computer's identity"
|
||||||
#~ msgstr "Use this computer's identity"
|
#~ msgstr "Use this computer's identity"
|
||||||
|
|
||||||
#~ msgid "On this computer"
|
|
||||||
#~ msgstr "On this computer"
|
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
#~ "Where Proton Drive lives on this machine. Synced folders keep a full copy "
|
#~ "Where Proton Drive lives on this machine. Synced folders keep a full copy "
|
||||||
#~ "on this disk; on-demand folders keep the files in Proton Drive only and "
|
#~ "on this disk; on-demand folders keep the files in Proton Drive only and "
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: proton-drive-linux 2.1.0\n"
|
"Project-Id-Version: proton-drive-linux 2.1.0\n"
|
||||||
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
||||||
"POT-Creation-Date: 2026-09-28 16:17+0200\n"
|
"POT-Creation-Date: 2026-09-28 22:51+0200\n"
|
||||||
"PO-Revision-Date: 2026-09-23 12:00+0200\n"
|
"PO-Revision-Date: 2026-09-23 12:00+0200\n"
|
||||||
"Last-Translator: Proton Drive for Linux contributors\n"
|
"Last-Translator: Proton Drive for Linux contributors\n"
|
||||||
"Language-Team: Spanish\n"
|
"Language-Team: Spanish\n"
|
||||||
@@ -57,7 +57,7 @@ msgstr "%-d %b %Y"
|
|||||||
msgid "%A, %B %-d, %Y, %H:%M"
|
msgid "%A, %B %-d, %Y, %H:%M"
|
||||||
msgstr "%A, %-d de %B de %Y, %H:%M"
|
msgstr "%A, %-d de %B de %Y, %H:%M"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:882
|
#: crates/pdfs-gui/src/app/main.rs:734
|
||||||
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
||||||
@@ -68,91 +68,91 @@ msgstr "%A, %-d de %B de %Y, %H:%M"
|
|||||||
msgid "My files"
|
msgid "My files"
|
||||||
msgstr "Mis archivos"
|
msgstr "Mis archivos"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:885 crates/pdfs-gui/src/app/main.rs:1529
|
#: crates/pdfs-gui/src/app/main.rs:737 crates/pdfs-gui/src/app/main.rs:1381
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
||||||
msgid "Photos"
|
msgid "Photos"
|
||||||
msgstr "Fotos"
|
msgstr "Fotos"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:890
|
#: crates/pdfs-gui/src/app/main.rs:742
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
||||||
msgid "Shared with me"
|
msgid "Shared with me"
|
||||||
msgstr "Compartido conmigo"
|
msgstr "Compartido conmigo"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:895
|
#: crates/pdfs-gui/src/app/main.rs:747
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
||||||
msgid "Shared by me"
|
msgid "Shared by me"
|
||||||
msgstr "Compartido por mí"
|
msgstr "Compartido por mí"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:898
|
#: crates/pdfs-gui/src/app/main.rs:750
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
||||||
msgid "Computers"
|
msgid "Computers"
|
||||||
msgstr "Equipos"
|
msgstr "Equipos"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:899
|
#: crates/pdfs-gui/src/app/main.rs:751
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
||||||
msgid "Trash"
|
msgid "Trash"
|
||||||
msgstr "Papelera"
|
msgstr "Papelera"
|
||||||
|
|
||||||
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:901
|
#: crates/pdfs-gui/src/app/main.rs:753
|
||||||
msgid "Sync"
|
msgid "Sync"
|
||||||
msgstr "Sincronización"
|
msgstr "Sincronización"
|
||||||
|
|
||||||
#. Translators: a failed action's toast. {what} says what failed, such as
|
#. Translators: a failed action's toast. {what} says what failed, such as
|
||||||
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
||||||
#. from the mount service.
|
#. from the mount service.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1218
|
#: crates/pdfs-gui/src/app/main.rs:1070
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{what}: {detail}"
|
msgid "{what}: {detail}"
|
||||||
msgstr "{what}: {detail}"
|
msgstr "{what}: {detail}"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1237
|
#: crates/pdfs-gui/src/app/main.rs:1089
|
||||||
msgid "You're offline"
|
msgid "You're offline"
|
||||||
msgstr "Está sin conexión"
|
msgstr "Está sin conexión"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1238
|
#: crates/pdfs-gui/src/app/main.rs:1090
|
||||||
msgid "That's not there any more"
|
msgid "That's not there any more"
|
||||||
msgstr "Eso ya no existe"
|
msgstr "Eso ya no existe"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1239
|
#: crates/pdfs-gui/src/app/main.rs:1091
|
||||||
msgid "You don't have access to that"
|
msgid "You don't have access to that"
|
||||||
msgstr "No tiene acceso a eso"
|
msgstr "No tiene acceso a eso"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1240
|
#: crates/pdfs-gui/src/app/main.rs:1092
|
||||||
msgid "Something changed this first"
|
msgid "Something changed this first"
|
||||||
msgstr "Algo lo cambió antes"
|
msgstr "Algo lo cambió antes"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1241
|
#: crates/pdfs-gui/src/app/main.rs:1093
|
||||||
msgid "Your Proton Drive is full"
|
msgid "Your Proton Drive is full"
|
||||||
msgstr "Su Proton Drive está lleno"
|
msgstr "Su Proton Drive está lleno"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1282 crates/pdfs-gui/src/app/main.rs:1511
|
#: crates/pdfs-gui/src/app/main.rs:1134 crates/pdfs-gui/src/app/main.rs:1363
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
||||||
msgid "Preferences"
|
msgid "Preferences"
|
||||||
msgstr "Preferencias"
|
msgstr "Preferencias"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1283
|
#: crates/pdfs-gui/src/app/main.rs:1135
|
||||||
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
||||||
msgid "Keyboard Shortcuts"
|
msgid "Keyboard Shortcuts"
|
||||||
msgstr "Atajos del teclado"
|
msgstr "Atajos del teclado"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1285
|
#: crates/pdfs-gui/src/app/main.rs:1137
|
||||||
msgid "About Proton Drive for Linux"
|
msgid "About Proton Drive for Linux"
|
||||||
msgstr "Acerca de Proton Drive for Linux"
|
msgstr "Acerca de Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1290
|
#: crates/pdfs-gui/src/app/main.rs:1142
|
||||||
msgid "Main menu"
|
msgid "Main menu"
|
||||||
msgstr "Menú principal"
|
msgstr "Menú principal"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1346
|
#: crates/pdfs-gui/src/app/main.rs:1198
|
||||||
msgid "Proton Drive for Linux"
|
msgid "Proton Drive for Linux"
|
||||||
msgstr "Proton Drive for Linux"
|
msgstr "Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1354
|
#: crates/pdfs-gui/src/app/main.rs:1206
|
||||||
msgid ""
|
msgid ""
|
||||||
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -164,23 +164,23 @@ msgstr ""
|
|||||||
"Cliente no oficial: no está afiliado a Proton AG, ni respaldado ni mantenido "
|
"Cliente no oficial: no está afiliado a Proton AG, ni respaldado ni mantenido "
|
||||||
"por esta."
|
"por esta."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1359
|
#: crates/pdfs-gui/src/app/main.rs:1211
|
||||||
msgid "Source Code"
|
msgid "Source Code"
|
||||||
msgstr "Código fuente"
|
msgstr "Código fuente"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1363
|
#: crates/pdfs-gui/src/app/main.rs:1215
|
||||||
msgid "Proton Terms of Service"
|
msgid "Proton Terms of Service"
|
||||||
msgstr "Condiciones del servicio de Proton"
|
msgstr "Condiciones del servicio de Proton"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1367
|
#: crates/pdfs-gui/src/app/main.rs:1219
|
||||||
msgid "Proton Privacy Policy"
|
msgid "Proton Privacy Policy"
|
||||||
msgstr "Política de privacidad de Proton"
|
msgstr "Política de privacidad de Proton"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1372
|
#: crates/pdfs-gui/src/app/main.rs:1224
|
||||||
msgid "Disclaimer"
|
msgid "Disclaimer"
|
||||||
msgstr "Aviso legal"
|
msgstr "Aviso legal"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1376
|
#: crates/pdfs-gui/src/app/main.rs:1228
|
||||||
msgid ""
|
msgid ""
|
||||||
"This is an unofficial, community-built client. It is not affiliated with, "
|
"This is an unofficial, community-built client. It is not affiliated with, "
|
||||||
"endorsed by, or supported by Proton AG.\n"
|
"endorsed by, or supported by Proton AG.\n"
|
||||||
@@ -203,7 +203,7 @@ msgstr ""
|
|||||||
"Este software se proporciona “tal cual”, sin garantía de ningún tipo. Guarde "
|
"Este software se proporciona “tal cual”, sin garantía de ningún tipo. Guarde "
|
||||||
"una copia de los datos que no pueda permitirse perder."
|
"una copia de los datos que no pueda permitirse perder."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1384
|
#: crates/pdfs-gui/src/app/main.rs:1236
|
||||||
msgid ""
|
msgid ""
|
||||||
"Places are named with data from <a href=\"https://www.geonames.org/"
|
"Places are named with data from <a href=\"https://www.geonames.org/"
|
||||||
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
||||||
@@ -213,7 +213,7 @@ msgstr ""
|
|||||||
"www.geonames.org/\">GeoNames</a>, con licencia <a href=\"https://"
|
"www.geonames.org/\">GeoNames</a>, con licencia <a href=\"https://"
|
||||||
"creativecommons.org/licenses/by/4.0/\">CC BY 4.0</a>."
|
"creativecommons.org/licenses/by/4.0/\">CC BY 4.0</a>."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1392
|
#: crates/pdfs-gui/src/app/main.rs:1244
|
||||||
msgid ""
|
msgid ""
|
||||||
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
||||||
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
||||||
@@ -223,7 +223,7 @@ msgstr ""
|
|||||||
"fronteras de países de <a href=\"https://www.naturalearthdata.com/\">Natural "
|
"fronteras de países de <a href=\"https://www.naturalearthdata.com/\">Natural "
|
||||||
"Earth</a>, que son de dominio público."
|
"Earth</a>, que son de dominio público."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1400
|
#: crates/pdfs-gui/src/app/main.rs:1252
|
||||||
msgid ""
|
msgid ""
|
||||||
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
||||||
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
||||||
@@ -239,15 +239,15 @@ msgstr ""
|
|||||||
|
|
||||||
#. Translators: replace with your name(s) and email address(es), one
|
#. Translators: replace with your name(s) and email address(es), one
|
||||||
#. per line, to be credited in the About dialog.
|
#. per line, to be credited in the About dialog.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1411
|
#: crates/pdfs-gui/src/app/main.rs:1263
|
||||||
msgid "translator-credits"
|
msgid "translator-credits"
|
||||||
msgstr "Colaboradores de Proton Drive for Linux"
|
msgstr "Colaboradores de Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1449
|
#: crates/pdfs-gui/src/app/main.rs:1301
|
||||||
msgid "Stop Proton Drive?"
|
msgid "Stop Proton Drive?"
|
||||||
msgstr "¿Detener Proton Drive?"
|
msgstr "¿Detener Proton Drive?"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1450
|
#: crates/pdfs-gui/src/app/main.rs:1302
|
||||||
msgid ""
|
msgid ""
|
||||||
"My files goes offline and nothing syncs until you connect again. Proton "
|
"My files goes offline and nothing syncs until you connect again. Proton "
|
||||||
"Drive starts again at your next login."
|
"Drive starts again at your next login."
|
||||||
@@ -256,7 +256,7 @@ msgstr ""
|
|||||||
"a conectarse. Proton Drive se iniciará de nuevo en su próximo inicio de "
|
"a conectarse. Proton Drive se iniciará de nuevo en su próximo inicio de "
|
||||||
"sesión."
|
"sesión."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1452 crates/pdfs-gui/src/app/main.rs:1727
|
#: crates/pdfs-gui/src/app/main.rs:1304 crates/pdfs-gui/src/app/main.rs:1579
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
||||||
@@ -285,30 +285,30 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Cancelar"
|
msgstr "Cancelar"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1453
|
#: crates/pdfs-gui/src/app/main.rs:1305
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Stop"
|
msgid "Stop"
|
||||||
msgstr "Detener"
|
msgstr "Detener"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1470
|
#: crates/pdfs-gui/src/app/main.rs:1322
|
||||||
msgid "Couldn't stop Proton Drive"
|
msgid "Couldn't stop Proton Drive"
|
||||||
msgstr "No se pudo detener Proton Drive"
|
msgstr "No se pudo detener Proton Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1471
|
#: crates/pdfs-gui/src/app/main.rs:1323
|
||||||
msgid "The Proton Drive service did not stop."
|
msgid "The Proton Drive service did not stop."
|
||||||
msgstr "El servicio de Proton Drive no se detuvo."
|
msgstr "El servicio de Proton Drive no se detuvo."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1507
|
#: crates/pdfs-gui/src/app/main.rs:1359
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
||||||
msgid "General"
|
msgid "General"
|
||||||
msgstr "General"
|
msgstr "General"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1509
|
#: crates/pdfs-gui/src/app/main.rs:1361
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
||||||
msgid "Search Drive"
|
msgid "Search Drive"
|
||||||
msgstr "Buscar en Drive"
|
msgstr "Buscar en Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1510
|
#: crates/pdfs-gui/src/app/main.rs:1362
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
||||||
@@ -317,38 +317,38 @@ msgstr "Buscar en Drive"
|
|||||||
msgid "Refresh"
|
msgid "Refresh"
|
||||||
msgstr "Actualizar"
|
msgstr "Actualizar"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1512
|
#: crates/pdfs-gui/src/app/main.rs:1364
|
||||||
msgid "Keyboard shortcuts"
|
msgid "Keyboard shortcuts"
|
||||||
msgstr "Atajos del teclado"
|
msgstr "Atajos del teclado"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1516
|
#: crates/pdfs-gui/src/app/main.rs:1368
|
||||||
msgid "Files"
|
msgid "Files"
|
||||||
msgstr "Archivos"
|
msgstr "Archivos"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1518
|
#: crates/pdfs-gui/src/app/main.rs:1370
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
||||||
msgid "New folder"
|
msgid "New folder"
|
||||||
msgstr "Carpeta nueva"
|
msgstr "Carpeta nueva"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1519
|
#: crates/pdfs-gui/src/app/main.rs:1371
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
||||||
msgid "Upload files"
|
msgid "Upload files"
|
||||||
msgstr "Subir archivos"
|
msgstr "Subir archivos"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1520
|
#: crates/pdfs-gui/src/app/main.rs:1372
|
||||||
msgid "Back / forward"
|
msgid "Back / forward"
|
||||||
msgstr "Atrás / adelante"
|
msgstr "Atrás / adelante"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1521
|
#: crates/pdfs-gui/src/app/main.rs:1373
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
||||||
msgid "Parent folder"
|
msgid "Parent folder"
|
||||||
msgstr "Carpeta superior"
|
msgstr "Carpeta superior"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1522
|
#: crates/pdfs-gui/src/app/main.rs:1374
|
||||||
msgid "Grid / list"
|
msgid "Grid / list"
|
||||||
msgstr "Cuadrícula / lista"
|
msgstr "Cuadrícula / lista"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1523
|
#: crates/pdfs-gui/src/app/main.rs:1375
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
||||||
@@ -357,7 +357,7 @@ msgstr "Cuadrícula / lista"
|
|||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
msgstr "Renombrar"
|
msgstr "Renombrar"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1524 crates/pdfs-gui/src/app/main.rs:1544
|
#: crates/pdfs-gui/src/app/main.rs:1376 crates/pdfs-gui/src/app/main.rs:1396
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
||||||
@@ -367,72 +367,73 @@ msgstr "Renombrar"
|
|||||||
msgid "Move to Trash"
|
msgid "Move to Trash"
|
||||||
msgstr "Mover a la papelera"
|
msgstr "Mover a la papelera"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1525
|
#: crates/pdfs-gui/src/app/main.rs:1377
|
||||||
msgid "Clear the selection"
|
msgid "Clear the selection"
|
||||||
msgstr "Anular la selección"
|
msgstr "Anular la selección"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1531
|
#: crates/pdfs-gui/src/app/main.rs:1383
|
||||||
msgid "Larger thumbnails"
|
msgid "Larger thumbnails"
|
||||||
msgstr "Miniaturas más grandes"
|
msgstr "Miniaturas más grandes"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1532
|
#: crates/pdfs-gui/src/app/main.rs:1384
|
||||||
msgid "Smaller thumbnails"
|
msgid "Smaller thumbnails"
|
||||||
msgstr "Miniaturas más pequeñas"
|
msgstr "Miniaturas más pequeñas"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1533
|
#: crates/pdfs-gui/src/app/main.rs:1385
|
||||||
msgid "Reset thumbnail size"
|
msgid "Reset thumbnail size"
|
||||||
msgstr "Restablecer el tamaño de las miniaturas"
|
msgstr "Restablecer el tamaño de las miniaturas"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1534
|
#: crates/pdfs-gui/src/app/main.rs:1386
|
||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Seleccionar todo"
|
msgstr "Seleccionar todo"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1538
|
#: crates/pdfs-gui/src/app/main.rs:1390
|
||||||
msgid "Photo Viewer"
|
msgid "Photo Viewer"
|
||||||
msgstr "Visor de fotos"
|
msgstr "Visor de fotos"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1540
|
#: crates/pdfs-gui/src/app/main.rs:1392
|
||||||
msgid "Previous / next photo"
|
msgid "Previous / next photo"
|
||||||
msgstr "Foto anterior / siguiente"
|
msgstr "Foto anterior / siguiente"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1541
|
#: crates/pdfs-gui/src/app/main.rs:1393
|
||||||
msgid "First / last photo"
|
msgid "First / last photo"
|
||||||
msgstr "Primera / última foto"
|
msgstr "Primera / última foto"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1542
|
#: crates/pdfs-gui/src/app/main.rs:1394
|
||||||
msgid "Show details"
|
msgid "Show details"
|
||||||
msgstr "Mostrar detalles"
|
msgstr "Mostrar detalles"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1543
|
#: crates/pdfs-gui/src/app/main.rs:1395
|
||||||
msgid "Fullscreen"
|
msgid "Fullscreen"
|
||||||
msgstr "Pantalla completa"
|
msgstr "Pantalla completa"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1545
|
#: crates/pdfs-gui/src/app/main.rs:1397
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "Cerrar"
|
msgstr "Cerrar"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1635
|
#: crates/pdfs-gui/src/app/main.rs:1487
|
||||||
msgid "Select a single item to rename it"
|
msgid "Select a single item to rename it"
|
||||||
msgstr "Seleccione un único elemento para renombrarlo"
|
msgstr "Seleccione un único elemento para renombrarlo"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1795
|
#: crates/pdfs-gui/src/app/main.rs:1647
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
||||||
msgid "Not connected"
|
msgid "Not connected"
|
||||||
msgstr "Sin conexión"
|
msgstr "Sin conexión"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1796
|
#: crates/pdfs-gui/src/app/main.rs:1648
|
||||||
msgid "The Proton Drive service isn't running."
|
msgid "The Proton Drive service isn't running."
|
||||||
msgstr "El servicio de Proton Drive no está en ejecución."
|
msgstr "El servicio de Proton Drive no está en ejecución."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1804
|
#: crates/pdfs-gui/src/app/main.rs:1656
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:336 crates/pdfs-gui/src/main.rs:444
|
#: crates/pdfs-gui/src/app/pages/status.rs:336
|
||||||
#: crates/pdfs-gui/src/prompt.rs:616 crates/pdfs-gui/src/prompt.rs:867
|
#: crates/pdfs-gui/src/hud/mod.rs:274 crates/pdfs-gui/src/hud/mod.rs:589
|
||||||
|
#: crates/pdfs-gui/src/main.rs:444
|
||||||
msgid "Connecting…"
|
msgid "Connecting…"
|
||||||
msgstr "Conectando…"
|
msgstr "Conectando…"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1805
|
#: crates/pdfs-gui/src/app/main.rs:1657
|
||||||
msgid "Waiting for the Proton Drive service to start."
|
msgid "Waiting for the Proton Drive service to start."
|
||||||
msgstr "Esperando a que se inicie el servicio de Proton Drive."
|
msgstr "Esperando a que se inicie el servicio de Proton Drive."
|
||||||
|
|
||||||
@@ -477,7 +478,7 @@ msgstr "Nada del registro reciente coincide con este filtro."
|
|||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/prompt.rs:595
|
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/hud/mod.rs:245
|
||||||
msgid "Retry"
|
msgid "Retry"
|
||||||
msgstr "Reintentar"
|
msgstr "Reintentar"
|
||||||
|
|
||||||
@@ -1236,6 +1237,7 @@ msgstr "El servicio de Proton Drive no respondió."
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:265
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
msgstr "Abrir"
|
msgstr "Abrir"
|
||||||
@@ -1426,6 +1428,7 @@ msgstr "Nombre"
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:133
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Size"
|
msgid "Size"
|
||||||
msgstr "Tamaño"
|
msgstr "Tamaño"
|
||||||
@@ -1601,6 +1604,7 @@ msgstr[1] "{n} archivos ahora están solo en línea"
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:134
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Modified"
|
msgid "Modified"
|
||||||
msgstr "Modificado"
|
msgstr "Modificado"
|
||||||
@@ -1609,6 +1613,7 @@ msgstr "Modificado"
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:135
|
||||||
msgid "Location"
|
msgid "Location"
|
||||||
msgstr "Ubicación"
|
msgstr "Ubicación"
|
||||||
|
|
||||||
@@ -1627,6 +1632,7 @@ msgstr "Reproducir"
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:296
|
||||||
msgid "Available offline"
|
msgid "Available offline"
|
||||||
msgstr "Disponible sin conexión"
|
msgstr "Disponible sin conexión"
|
||||||
|
|
||||||
@@ -2047,7 +2053,6 @@ msgstr "Obtener Proton Drive"
|
|||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
||||||
#: crates/pdfs-gui/src/prompt.rs:568
|
|
||||||
msgid "This computer"
|
msgid "This computer"
|
||||||
msgstr "Este equipo"
|
msgstr "Este equipo"
|
||||||
|
|
||||||
@@ -2533,7 +2538,7 @@ msgid "About Sync Modes"
|
|||||||
msgstr "Acerca de los modos de sincronización"
|
msgstr "Acerca de los modos de sincronización"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
||||||
#: crates/pdfs-gui/src/prompt.rs:60
|
#: crates/pdfs-gui/src/prompt.rs:65
|
||||||
msgid "Folders"
|
msgid "Folders"
|
||||||
msgstr "Carpetas"
|
msgstr "Carpetas"
|
||||||
|
|
||||||
@@ -3407,7 +3412,7 @@ msgid "Select Photo to Upload"
|
|||||||
msgstr "Seleccionar la foto que subir"
|
msgstr "Seleccionar la foto que subir"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
||||||
#: crates/pdfs-gui/src/prompt.rs:62
|
#: crates/pdfs-gui/src/prompt.rs:67
|
||||||
msgid "Images"
|
msgid "Images"
|
||||||
msgstr "Imágenes"
|
msgstr "Imágenes"
|
||||||
|
|
||||||
@@ -4570,7 +4575,7 @@ msgid "Up to date"
|
|||||||
msgstr "Actualizado"
|
msgstr "Actualizado"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
||||||
#: crates/pdfs-gui/src/prompt.rs:880 crates/pdfs-gui/src/prompt.rs:893
|
#: crates/pdfs-gui/src/hud/mod.rs:602 crates/pdfs-gui/src/hud/mod.rs:616
|
||||||
msgid "Proton Drive isn't running"
|
msgid "Proton Drive isn't running"
|
||||||
msgstr "Proton Drive no está en ejecución"
|
msgstr "Proton Drive no está en ejecución"
|
||||||
|
|
||||||
@@ -5112,6 +5117,7 @@ msgid "Proton Drive"
|
|||||||
msgstr "Proton Drive"
|
msgstr "Proton Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:263
|
||||||
msgid "Open folder"
|
msgid "Open folder"
|
||||||
msgstr "Abrir carpeta"
|
msgstr "Abrir carpeta"
|
||||||
|
|
||||||
@@ -5495,6 +5501,138 @@ msgstr "(no hay archivos anclados; escriba para buscar)"
|
|||||||
msgid "(cannot resolve app dirs)"
|
msgid "(cannot resolve app dirs)"
|
||||||
msgstr "(no se pueden resolver las carpetas de la aplicación)"
|
msgstr "(no se pueden resolver las carpetas de la aplicación)"
|
||||||
|
|
||||||
|
#. Translators: the launcher's section of recently opened files.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:57
|
||||||
|
msgid "Recent"
|
||||||
|
msgstr "Recientes"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:58
|
||||||
|
msgid "Pinned in Proton Drive"
|
||||||
|
msgstr "Anclado en Proton Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:59
|
||||||
|
msgid "Best matches"
|
||||||
|
msgstr "Mejores coincidencias"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:159
|
||||||
|
msgid "Search Proton Drive"
|
||||||
|
msgstr "Buscar en Proton Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:180
|
||||||
|
msgid "Search in Drive and on this computer"
|
||||||
|
msgstr "Buscar en Drive y en este equipo"
|
||||||
|
|
||||||
|
#. Translators: the Escape key, shown as a hint that it closes the window.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:191
|
||||||
|
msgid "Esc"
|
||||||
|
msgstr "Esc"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:232 crates/pdfs-gui/src/hud/mod.rs:862
|
||||||
|
msgid "No results"
|
||||||
|
msgstr "No hay resultados"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:240
|
||||||
|
msgid "Proton Drive is not running"
|
||||||
|
msgstr "Proton Drive no está en ejecución"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:241
|
||||||
|
msgid "Start Proton Drive to search your files."
|
||||||
|
msgstr "Inicie Proton Drive para buscar en sus archivos."
|
||||||
|
|
||||||
|
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:282
|
||||||
|
msgid "↑↓ navigate · ↵ open · Tab filter"
|
||||||
|
msgstr "↑↓ navegar · ↵ abrir · Tab filtrar"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:864
|
||||||
|
msgid "Search your Drive"
|
||||||
|
msgstr "Buscar en su Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:869
|
||||||
|
msgid "Still indexing this computer — local results will fill in shortly."
|
||||||
|
msgstr ""
|
||||||
|
"Todavía se está indexando este equipo; los resultados locales aparecerán en "
|
||||||
|
"breve."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:871
|
||||||
|
msgid "Try a different search, or another filter."
|
||||||
|
msgstr "Pruebe con otra búsqueda u otro filtro."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:873
|
||||||
|
msgid "Start typing to search Proton Drive and the files on this computer."
|
||||||
|
msgstr ""
|
||||||
|
"Empiece a escribir para buscar en Proton Drive y en los archivos de este "
|
||||||
|
"equipo."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:900
|
||||||
|
msgid "No pinned files"
|
||||||
|
msgstr "No hay archivos anclados"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:901
|
||||||
|
#, rust-format
|
||||||
|
msgid "{n} pinned file"
|
||||||
|
msgid_plural "{n} pinned files"
|
||||||
|
msgstr[0] "{n} archivo anclado"
|
||||||
|
msgstr[1] "{n} archivos anclados"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:914
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
||||||
|
msgstr "{drive} en Drive · {local} en este equipo · indexando…"
|
||||||
|
|
||||||
|
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:919
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer"
|
||||||
|
msgstr "{drive} en Drive · {local} en este equipo"
|
||||||
|
|
||||||
|
#. Translators: {name} is a file name.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1050
|
||||||
|
#, rust-format
|
||||||
|
msgid "Opening {name}…"
|
||||||
|
msgstr "Abriendo {name}…"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1090
|
||||||
|
#, rust-format
|
||||||
|
msgid "Could not open: {message}"
|
||||||
|
msgstr "No se pudo abrir: {message}"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1094
|
||||||
|
msgid "Couldn't reach Proton Drive"
|
||||||
|
msgstr "No se pudo contactar con Proton Drive"
|
||||||
|
|
||||||
|
#. Translators: a heading in the launcher's preview pane, followed by
|
||||||
|
#. "Available offline", "Downloaded", "Online only" or "On this computer".
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:138
|
||||||
|
msgid "Availability"
|
||||||
|
msgstr "Disponibilidad"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:157
|
||||||
|
msgid "Show in Folder"
|
||||||
|
msgstr "Mostrar en la carpeta"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:160
|
||||||
|
msgid "Copy Path"
|
||||||
|
msgstr "Copiar ruta"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:166
|
||||||
|
msgid "Path copied"
|
||||||
|
msgstr "Ruta copiada"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:291
|
||||||
|
msgid "On this computer"
|
||||||
|
msgstr "En este equipo"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:303
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Downloaded"
|
||||||
|
msgstr "Descargado"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:308
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Online only"
|
||||||
|
msgstr "Solo en línea"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/i18n.rs:84
|
#: crates/pdfs-gui/src/i18n.rs:84
|
||||||
msgid "Kosovo"
|
msgid "Kosovo"
|
||||||
msgstr "Kosovo"
|
msgstr "Kosovo"
|
||||||
@@ -5648,16 +5786,16 @@ msgid "Quit"
|
|||||||
msgstr "Salir"
|
msgstr "Salir"
|
||||||
|
|
||||||
#. Translators: a filter chip that shows every kind of result.
|
#. Translators: a filter chip that shows every kind of result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:59
|
#: crates/pdfs-gui/src/prompt.rs:64
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Todo"
|
msgstr "Todo"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:61
|
#: crates/pdfs-gui/src/prompt.rs:66
|
||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Documentos"
|
msgstr "Documentos"
|
||||||
|
|
||||||
#. Translators: a filter chip for audio and video files.
|
#. Translators: a filter chip for audio and video files.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:64
|
#: crates/pdfs-gui/src/prompt.rs:69
|
||||||
msgid "Media"
|
msgid "Media"
|
||||||
msgstr "Multimedia"
|
msgstr "Multimedia"
|
||||||
|
|
||||||
@@ -5666,144 +5804,49 @@ msgstr "Multimedia"
|
|||||||
msgid "My files / {path}"
|
msgid "My files / {path}"
|
||||||
msgstr "Mis archivos / {path}"
|
msgstr "Mis archivos / {path}"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:520
|
|
||||||
msgid "Search Proton Drive"
|
|
||||||
msgstr "Buscar en Proton Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:541
|
|
||||||
msgid "Search in Drive and on this computer"
|
|
||||||
msgstr "Buscar en Drive y en este equipo"
|
|
||||||
|
|
||||||
#. Translators: the Escape key, shown as a hint that it closes the window.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:552
|
|
||||||
msgid "Esc"
|
|
||||||
msgstr "Esc"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:566 crates/pdfs-gui/src/prompt.rs:1085
|
|
||||||
msgid "Best matches"
|
|
||||||
msgstr "Mejores coincidencias"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:582 crates/pdfs-gui/src/prompt.rs:1109
|
|
||||||
msgid "No results"
|
|
||||||
msgstr "No hay resultados"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:590
|
|
||||||
msgid "Proton Drive is not running"
|
|
||||||
msgstr "Proton Drive no está en ejecución"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:591
|
|
||||||
msgid "Start Proton Drive to search your files."
|
|
||||||
msgstr "Inicie Proton Drive para buscar en sus archivos."
|
|
||||||
|
|
||||||
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:624
|
|
||||||
msgid "↑↓ navigate · ↵ open · Tab filter"
|
|
||||||
msgstr "↑↓ navegar · ↵ abrir · Tab filtrar"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1096
|
|
||||||
msgid "Pinned in Proton Drive"
|
|
||||||
msgstr "Anclado en Proton Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1111
|
|
||||||
msgid "Search your Drive"
|
|
||||||
msgstr "Buscar en su Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1116
|
|
||||||
msgid "Still indexing this computer — local results will fill in shortly."
|
|
||||||
msgstr ""
|
|
||||||
"Todavía se está indexando este equipo; los resultados locales aparecerán en "
|
|
||||||
"breve."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1118
|
|
||||||
msgid "Try a different search, or another filter."
|
|
||||||
msgstr "Pruebe con otra búsqueda u otro filtro."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1120
|
|
||||||
msgid "Start typing to search Proton Drive and the files on this computer."
|
|
||||||
msgstr ""
|
|
||||||
"Empiece a escribir para buscar en Proton Drive y en los archivos de este "
|
|
||||||
"equipo."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1146
|
|
||||||
msgid "No pinned files"
|
|
||||||
msgstr "No hay archivos anclados"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1147
|
|
||||||
#, rust-format
|
|
||||||
msgid "{n} pinned file"
|
|
||||||
msgid_plural "{n} pinned files"
|
|
||||||
msgstr[0] "{n} archivo anclado"
|
|
||||||
msgstr[1] "{n} archivos anclados"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1156
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
|
||||||
msgstr "{drive} en Drive · {local} en este equipo · indexando…"
|
|
||||||
|
|
||||||
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1161
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer"
|
|
||||||
msgstr "{drive} en Drive · {local} en este equipo"
|
|
||||||
|
|
||||||
#. Translators: {name} is a file name.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1294
|
|
||||||
#, rust-format
|
|
||||||
msgid "Opening {name}…"
|
|
||||||
msgstr "Abriendo {name}…"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1317
|
|
||||||
#, rust-format
|
|
||||||
msgid "Could not open: {message}"
|
|
||||||
msgstr "No se pudo abrir: {message}"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1321
|
|
||||||
msgid "Couldn't reach Proton Drive"
|
|
||||||
msgstr "No se pudo contactar con Proton Drive"
|
|
||||||
|
|
||||||
#. Translators: the user's home folder, as the location of a result.
|
#. Translators: the user's home folder, as the location of a result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1430
|
#: crates/pdfs-gui/src/prompt.rs:455
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Carpeta personal"
|
msgstr "Carpeta personal"
|
||||||
|
|
||||||
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1432
|
#: crates/pdfs-gui/src/prompt.rs:457
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "Home / {path}"
|
msgid "Home / {path}"
|
||||||
msgstr "Carpeta personal / {path}"
|
msgstr "Carpeta personal / {path}"
|
||||||
|
|
||||||
#. Translators: the age of a file modified under a minute ago.
|
#. Translators: the age of a file modified under a minute ago.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1452
|
#: crates/pdfs-gui/src/prompt.rs:477
|
||||||
msgctxt "age"
|
msgctxt "age"
|
||||||
msgid "now"
|
msgid "now"
|
||||||
msgstr "ahora"
|
msgstr "ahora"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of minutes.
|
#. Translators: a compact file age; {count} is a number of minutes.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1454
|
#: crates/pdfs-gui/src/prompt.rs:479
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}m"
|
msgid "{count}m"
|
||||||
msgstr "{count} min"
|
msgstr "{count} min"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of hours.
|
#. Translators: a compact file age; {count} is a number of hours.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1456
|
#: crates/pdfs-gui/src/prompt.rs:481
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}h"
|
msgid "{count}h"
|
||||||
msgstr "{count} h"
|
msgstr "{count} h"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of days.
|
#. Translators: a compact file age; {count} is a number of days.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1458
|
#: crates/pdfs-gui/src/prompt.rs:483
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}d"
|
msgid "{count}d"
|
||||||
msgstr "{count} d"
|
msgstr "{count} d"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of months.
|
#. Translators: a compact file age; {count} is a number of months.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1460
|
#: crates/pdfs-gui/src/prompt.rs:485
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}mo"
|
msgid "{count}mo"
|
||||||
msgstr "{count} mes."
|
msgstr "{count} mes."
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of years.
|
#. Translators: a compact file age; {count} is a number of years.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1462
|
#: crates/pdfs-gui/src/prompt.rs:487
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}y"
|
msgid "{count}y"
|
||||||
msgstr "{count} a"
|
msgstr "{count} a"
|
||||||
@@ -5954,9 +5997,6 @@ msgstr "no se puede contactar con el demonio de Proton Drive: {error}"
|
|||||||
#~ msgid "Use this computer's identity"
|
#~ msgid "Use this computer's identity"
|
||||||
#~ msgstr "Usar la identidad de este equipo"
|
#~ msgstr "Usar la identidad de este equipo"
|
||||||
|
|
||||||
#~ msgid "On this computer"
|
|
||||||
#~ msgstr "En este equipo"
|
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
#~ "Where Proton Drive lives on this machine. Synced folders keep a full copy "
|
#~ "Where Proton Drive lives on this machine. Synced folders keep a full copy "
|
||||||
#~ "on this disk; on-demand folders keep the files in Proton Drive only and "
|
#~ "on this disk; on-demand folders keep the files in Proton Drive only and "
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: proton-drive-linux 2.1.0\n"
|
"Project-Id-Version: proton-drive-linux 2.1.0\n"
|
||||||
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
||||||
"POT-Creation-Date: 2026-09-28 16:17+0200\n"
|
"POT-Creation-Date: 2026-09-28 22:51+0200\n"
|
||||||
"PO-Revision-Date: 2026-09-23 12:00+0200\n"
|
"PO-Revision-Date: 2026-09-23 12:00+0200\n"
|
||||||
"Last-Translator: Proton Drive for Linux contributors\n"
|
"Last-Translator: Proton Drive for Linux contributors\n"
|
||||||
"Language-Team: French\n"
|
"Language-Team: French\n"
|
||||||
@@ -57,7 +57,7 @@ msgstr "%-d %b %Y"
|
|||||||
msgid "%A, %B %-d, %Y, %H:%M"
|
msgid "%A, %B %-d, %Y, %H:%M"
|
||||||
msgstr "%A %-d %B %Y à %H:%M"
|
msgstr "%A %-d %B %Y à %H:%M"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:882
|
#: crates/pdfs-gui/src/app/main.rs:734
|
||||||
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
||||||
@@ -68,91 +68,91 @@ msgstr "%A %-d %B %Y à %H:%M"
|
|||||||
msgid "My files"
|
msgid "My files"
|
||||||
msgstr "Mes fichiers"
|
msgstr "Mes fichiers"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:885 crates/pdfs-gui/src/app/main.rs:1529
|
#: crates/pdfs-gui/src/app/main.rs:737 crates/pdfs-gui/src/app/main.rs:1381
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
||||||
msgid "Photos"
|
msgid "Photos"
|
||||||
msgstr "Photos"
|
msgstr "Photos"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:890
|
#: crates/pdfs-gui/src/app/main.rs:742
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
||||||
msgid "Shared with me"
|
msgid "Shared with me"
|
||||||
msgstr "Partagés avec moi"
|
msgstr "Partagés avec moi"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:895
|
#: crates/pdfs-gui/src/app/main.rs:747
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
||||||
msgid "Shared by me"
|
msgid "Shared by me"
|
||||||
msgstr "Partagés par moi"
|
msgstr "Partagés par moi"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:898
|
#: crates/pdfs-gui/src/app/main.rs:750
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
||||||
msgid "Computers"
|
msgid "Computers"
|
||||||
msgstr "Ordinateurs"
|
msgstr "Ordinateurs"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:899
|
#: crates/pdfs-gui/src/app/main.rs:751
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
||||||
msgid "Trash"
|
msgid "Trash"
|
||||||
msgstr "Corbeille"
|
msgstr "Corbeille"
|
||||||
|
|
||||||
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:901
|
#: crates/pdfs-gui/src/app/main.rs:753
|
||||||
msgid "Sync"
|
msgid "Sync"
|
||||||
msgstr "Synchronisation"
|
msgstr "Synchronisation"
|
||||||
|
|
||||||
#. Translators: a failed action's toast. {what} says what failed, such as
|
#. Translators: a failed action's toast. {what} says what failed, such as
|
||||||
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
||||||
#. from the mount service.
|
#. from the mount service.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1218
|
#: crates/pdfs-gui/src/app/main.rs:1070
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{what}: {detail}"
|
msgid "{what}: {detail}"
|
||||||
msgstr "{what} : {detail}"
|
msgstr "{what} : {detail}"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1237
|
#: crates/pdfs-gui/src/app/main.rs:1089
|
||||||
msgid "You're offline"
|
msgid "You're offline"
|
||||||
msgstr "Vous êtes hors ligne"
|
msgstr "Vous êtes hors ligne"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1238
|
#: crates/pdfs-gui/src/app/main.rs:1090
|
||||||
msgid "That's not there any more"
|
msgid "That's not there any more"
|
||||||
msgstr "Cet élément n’existe plus"
|
msgstr "Cet élément n’existe plus"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1239
|
#: crates/pdfs-gui/src/app/main.rs:1091
|
||||||
msgid "You don't have access to that"
|
msgid "You don't have access to that"
|
||||||
msgstr "Vous n’avez pas accès à cet élément"
|
msgstr "Vous n’avez pas accès à cet élément"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1240
|
#: crates/pdfs-gui/src/app/main.rs:1092
|
||||||
msgid "Something changed this first"
|
msgid "Something changed this first"
|
||||||
msgstr "Quelque chose l’a modifié entre-temps"
|
msgstr "Quelque chose l’a modifié entre-temps"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1241
|
#: crates/pdfs-gui/src/app/main.rs:1093
|
||||||
msgid "Your Proton Drive is full"
|
msgid "Your Proton Drive is full"
|
||||||
msgstr "Votre Proton Drive est plein"
|
msgstr "Votre Proton Drive est plein"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1282 crates/pdfs-gui/src/app/main.rs:1511
|
#: crates/pdfs-gui/src/app/main.rs:1134 crates/pdfs-gui/src/app/main.rs:1363
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
||||||
msgid "Preferences"
|
msgid "Preferences"
|
||||||
msgstr "Préférences"
|
msgstr "Préférences"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1283
|
#: crates/pdfs-gui/src/app/main.rs:1135
|
||||||
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
||||||
msgid "Keyboard Shortcuts"
|
msgid "Keyboard Shortcuts"
|
||||||
msgstr "Raccourcis clavier"
|
msgstr "Raccourcis clavier"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1285
|
#: crates/pdfs-gui/src/app/main.rs:1137
|
||||||
msgid "About Proton Drive for Linux"
|
msgid "About Proton Drive for Linux"
|
||||||
msgstr "À propos de Proton Drive for Linux"
|
msgstr "À propos de Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1290
|
#: crates/pdfs-gui/src/app/main.rs:1142
|
||||||
msgid "Main menu"
|
msgid "Main menu"
|
||||||
msgstr "Menu principal"
|
msgstr "Menu principal"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1346
|
#: crates/pdfs-gui/src/app/main.rs:1198
|
||||||
msgid "Proton Drive for Linux"
|
msgid "Proton Drive for Linux"
|
||||||
msgstr "Proton Drive for Linux"
|
msgstr "Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1354
|
#: crates/pdfs-gui/src/app/main.rs:1206
|
||||||
msgid ""
|
msgid ""
|
||||||
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -164,23 +164,23 @@ msgstr ""
|
|||||||
"Client non officiel — non affilié à Proton AG, ni approuvé ni pris en charge "
|
"Client non officiel — non affilié à Proton AG, ni approuvé ni pris en charge "
|
||||||
"par Proton AG."
|
"par Proton AG."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1359
|
#: crates/pdfs-gui/src/app/main.rs:1211
|
||||||
msgid "Source Code"
|
msgid "Source Code"
|
||||||
msgstr "Code source"
|
msgstr "Code source"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1363
|
#: crates/pdfs-gui/src/app/main.rs:1215
|
||||||
msgid "Proton Terms of Service"
|
msgid "Proton Terms of Service"
|
||||||
msgstr "Conditions d’utilisation de Proton"
|
msgstr "Conditions d’utilisation de Proton"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1367
|
#: crates/pdfs-gui/src/app/main.rs:1219
|
||||||
msgid "Proton Privacy Policy"
|
msgid "Proton Privacy Policy"
|
||||||
msgstr "Politique de confidentialité de Proton"
|
msgstr "Politique de confidentialité de Proton"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1372
|
#: crates/pdfs-gui/src/app/main.rs:1224
|
||||||
msgid "Disclaimer"
|
msgid "Disclaimer"
|
||||||
msgstr "Avertissement"
|
msgstr "Avertissement"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1376
|
#: crates/pdfs-gui/src/app/main.rs:1228
|
||||||
msgid ""
|
msgid ""
|
||||||
"This is an unofficial, community-built client. It is not affiliated with, "
|
"This is an unofficial, community-built client. It is not affiliated with, "
|
||||||
"endorsed by, or supported by Proton AG.\n"
|
"endorsed by, or supported by Proton AG.\n"
|
||||||
@@ -204,7 +204,7 @@ msgstr ""
|
|||||||
"une copie de toutes les données que vous ne pouvez pas vous permettre de "
|
"une copie de toutes les données que vous ne pouvez pas vous permettre de "
|
||||||
"perdre."
|
"perdre."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1384
|
#: crates/pdfs-gui/src/app/main.rs:1236
|
||||||
msgid ""
|
msgid ""
|
||||||
"Places are named with data from <a href=\"https://www.geonames.org/"
|
"Places are named with data from <a href=\"https://www.geonames.org/"
|
||||||
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
||||||
@@ -214,7 +214,7 @@ msgstr ""
|
|||||||
"www.geonames.org/\">GeoNames</a>, sous licence <a href=\"https://"
|
"www.geonames.org/\">GeoNames</a>, sous licence <a href=\"https://"
|
||||||
"creativecommons.org/licenses/by/4.0/\">CC BY 4.0</a>."
|
"creativecommons.org/licenses/by/4.0/\">CC BY 4.0</a>."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1392
|
#: crates/pdfs-gui/src/app/main.rs:1244
|
||||||
msgid ""
|
msgid ""
|
||||||
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
||||||
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
||||||
@@ -224,7 +224,7 @@ msgstr ""
|
|||||||
"frontières des pays de <a href=\"https://www.naturalearthdata.com/\">Natural "
|
"frontières des pays de <a href=\"https://www.naturalearthdata.com/\">Natural "
|
||||||
"Earth</a>, qui sont dans le domaine public."
|
"Earth</a>, qui sont dans le domaine public."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1400
|
#: crates/pdfs-gui/src/app/main.rs:1252
|
||||||
msgid ""
|
msgid ""
|
||||||
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
||||||
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
||||||
@@ -241,15 +241,15 @@ msgstr ""
|
|||||||
|
|
||||||
#. Translators: replace with your name(s) and email address(es), one
|
#. Translators: replace with your name(s) and email address(es), one
|
||||||
#. per line, to be credited in the About dialog.
|
#. per line, to be credited in the About dialog.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1411
|
#: crates/pdfs-gui/src/app/main.rs:1263
|
||||||
msgid "translator-credits"
|
msgid "translator-credits"
|
||||||
msgstr "Les contributeurs de Proton Drive for Linux"
|
msgstr "Les contributeurs de Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1449
|
#: crates/pdfs-gui/src/app/main.rs:1301
|
||||||
msgid "Stop Proton Drive?"
|
msgid "Stop Proton Drive?"
|
||||||
msgstr "Arrêter Proton Drive ?"
|
msgstr "Arrêter Proton Drive ?"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1450
|
#: crates/pdfs-gui/src/app/main.rs:1302
|
||||||
msgid ""
|
msgid ""
|
||||||
"My files goes offline and nothing syncs until you connect again. Proton "
|
"My files goes offline and nothing syncs until you connect again. Proton "
|
||||||
"Drive starts again at your next login."
|
"Drive starts again at your next login."
|
||||||
@@ -258,7 +258,7 @@ msgstr ""
|
|||||||
"vous reconnectez pas. Proton Drive redémarrera à votre prochaine ouverture "
|
"vous reconnectez pas. Proton Drive redémarrera à votre prochaine ouverture "
|
||||||
"de session."
|
"de session."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1452 crates/pdfs-gui/src/app/main.rs:1727
|
#: crates/pdfs-gui/src/app/main.rs:1304 crates/pdfs-gui/src/app/main.rs:1579
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
||||||
@@ -287,30 +287,30 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Annuler"
|
msgstr "Annuler"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1453
|
#: crates/pdfs-gui/src/app/main.rs:1305
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Stop"
|
msgid "Stop"
|
||||||
msgstr "Arrêter"
|
msgstr "Arrêter"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1470
|
#: crates/pdfs-gui/src/app/main.rs:1322
|
||||||
msgid "Couldn't stop Proton Drive"
|
msgid "Couldn't stop Proton Drive"
|
||||||
msgstr "Impossible d’arrêter Proton Drive"
|
msgstr "Impossible d’arrêter Proton Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1471
|
#: crates/pdfs-gui/src/app/main.rs:1323
|
||||||
msgid "The Proton Drive service did not stop."
|
msgid "The Proton Drive service did not stop."
|
||||||
msgstr "Le service Proton Drive ne s’est pas arrêté."
|
msgstr "Le service Proton Drive ne s’est pas arrêté."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1507
|
#: crates/pdfs-gui/src/app/main.rs:1359
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
||||||
msgid "General"
|
msgid "General"
|
||||||
msgstr "Général"
|
msgstr "Général"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1509
|
#: crates/pdfs-gui/src/app/main.rs:1361
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
||||||
msgid "Search Drive"
|
msgid "Search Drive"
|
||||||
msgstr "Rechercher dans Drive"
|
msgstr "Rechercher dans Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1510
|
#: crates/pdfs-gui/src/app/main.rs:1362
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
||||||
@@ -319,38 +319,38 @@ msgstr "Rechercher dans Drive"
|
|||||||
msgid "Refresh"
|
msgid "Refresh"
|
||||||
msgstr "Actualiser"
|
msgstr "Actualiser"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1512
|
#: crates/pdfs-gui/src/app/main.rs:1364
|
||||||
msgid "Keyboard shortcuts"
|
msgid "Keyboard shortcuts"
|
||||||
msgstr "Raccourcis clavier"
|
msgstr "Raccourcis clavier"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1516
|
#: crates/pdfs-gui/src/app/main.rs:1368
|
||||||
msgid "Files"
|
msgid "Files"
|
||||||
msgstr "Fichiers"
|
msgstr "Fichiers"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1518
|
#: crates/pdfs-gui/src/app/main.rs:1370
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
||||||
msgid "New folder"
|
msgid "New folder"
|
||||||
msgstr "Nouveau dossier"
|
msgstr "Nouveau dossier"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1519
|
#: crates/pdfs-gui/src/app/main.rs:1371
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
||||||
msgid "Upload files"
|
msgid "Upload files"
|
||||||
msgstr "Téléverser des fichiers"
|
msgstr "Téléverser des fichiers"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1520
|
#: crates/pdfs-gui/src/app/main.rs:1372
|
||||||
msgid "Back / forward"
|
msgid "Back / forward"
|
||||||
msgstr "Précédent / suivant"
|
msgstr "Précédent / suivant"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1521
|
#: crates/pdfs-gui/src/app/main.rs:1373
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
||||||
msgid "Parent folder"
|
msgid "Parent folder"
|
||||||
msgstr "Dossier parent"
|
msgstr "Dossier parent"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1522
|
#: crates/pdfs-gui/src/app/main.rs:1374
|
||||||
msgid "Grid / list"
|
msgid "Grid / list"
|
||||||
msgstr "Grille / liste"
|
msgstr "Grille / liste"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1523
|
#: crates/pdfs-gui/src/app/main.rs:1375
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
||||||
@@ -359,7 +359,7 @@ msgstr "Grille / liste"
|
|||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
msgstr "Renommer"
|
msgstr "Renommer"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1524 crates/pdfs-gui/src/app/main.rs:1544
|
#: crates/pdfs-gui/src/app/main.rs:1376 crates/pdfs-gui/src/app/main.rs:1396
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
||||||
@@ -369,72 +369,73 @@ msgstr "Renommer"
|
|||||||
msgid "Move to Trash"
|
msgid "Move to Trash"
|
||||||
msgstr "Mettre à la corbeille"
|
msgstr "Mettre à la corbeille"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1525
|
#: crates/pdfs-gui/src/app/main.rs:1377
|
||||||
msgid "Clear the selection"
|
msgid "Clear the selection"
|
||||||
msgstr "Effacer la sélection"
|
msgstr "Effacer la sélection"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1531
|
#: crates/pdfs-gui/src/app/main.rs:1383
|
||||||
msgid "Larger thumbnails"
|
msgid "Larger thumbnails"
|
||||||
msgstr "Miniatures plus grandes"
|
msgstr "Miniatures plus grandes"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1532
|
#: crates/pdfs-gui/src/app/main.rs:1384
|
||||||
msgid "Smaller thumbnails"
|
msgid "Smaller thumbnails"
|
||||||
msgstr "Miniatures plus petites"
|
msgstr "Miniatures plus petites"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1533
|
#: crates/pdfs-gui/src/app/main.rs:1385
|
||||||
msgid "Reset thumbnail size"
|
msgid "Reset thumbnail size"
|
||||||
msgstr "Réinitialiser la taille des miniatures"
|
msgstr "Réinitialiser la taille des miniatures"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1534
|
#: crates/pdfs-gui/src/app/main.rs:1386
|
||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Tout sélectionner"
|
msgstr "Tout sélectionner"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1538
|
#: crates/pdfs-gui/src/app/main.rs:1390
|
||||||
msgid "Photo Viewer"
|
msgid "Photo Viewer"
|
||||||
msgstr "Visionneuse de photos"
|
msgstr "Visionneuse de photos"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1540
|
#: crates/pdfs-gui/src/app/main.rs:1392
|
||||||
msgid "Previous / next photo"
|
msgid "Previous / next photo"
|
||||||
msgstr "Photo précédente / suivante"
|
msgstr "Photo précédente / suivante"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1541
|
#: crates/pdfs-gui/src/app/main.rs:1393
|
||||||
msgid "First / last photo"
|
msgid "First / last photo"
|
||||||
msgstr "Première / dernière photo"
|
msgstr "Première / dernière photo"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1542
|
#: crates/pdfs-gui/src/app/main.rs:1394
|
||||||
msgid "Show details"
|
msgid "Show details"
|
||||||
msgstr "Afficher les détails"
|
msgstr "Afficher les détails"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1543
|
#: crates/pdfs-gui/src/app/main.rs:1395
|
||||||
msgid "Fullscreen"
|
msgid "Fullscreen"
|
||||||
msgstr "Plein écran"
|
msgstr "Plein écran"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1545
|
#: crates/pdfs-gui/src/app/main.rs:1397
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "Fermer"
|
msgstr "Fermer"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1635
|
#: crates/pdfs-gui/src/app/main.rs:1487
|
||||||
msgid "Select a single item to rename it"
|
msgid "Select a single item to rename it"
|
||||||
msgstr "Sélectionnez un seul élément pour le renommer"
|
msgstr "Sélectionnez un seul élément pour le renommer"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1795
|
#: crates/pdfs-gui/src/app/main.rs:1647
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
||||||
msgid "Not connected"
|
msgid "Not connected"
|
||||||
msgstr "Non connecté"
|
msgstr "Non connecté"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1796
|
#: crates/pdfs-gui/src/app/main.rs:1648
|
||||||
msgid "The Proton Drive service isn't running."
|
msgid "The Proton Drive service isn't running."
|
||||||
msgstr "Le service Proton Drive n’est pas en cours d’exécution."
|
msgstr "Le service Proton Drive n’est pas en cours d’exécution."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1804
|
#: crates/pdfs-gui/src/app/main.rs:1656
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:336 crates/pdfs-gui/src/main.rs:444
|
#: crates/pdfs-gui/src/app/pages/status.rs:336
|
||||||
#: crates/pdfs-gui/src/prompt.rs:616 crates/pdfs-gui/src/prompt.rs:867
|
#: crates/pdfs-gui/src/hud/mod.rs:274 crates/pdfs-gui/src/hud/mod.rs:589
|
||||||
|
#: crates/pdfs-gui/src/main.rs:444
|
||||||
msgid "Connecting…"
|
msgid "Connecting…"
|
||||||
msgstr "Connexion…"
|
msgstr "Connexion…"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1805
|
#: crates/pdfs-gui/src/app/main.rs:1657
|
||||||
msgid "Waiting for the Proton Drive service to start."
|
msgid "Waiting for the Proton Drive service to start."
|
||||||
msgstr "En attente du démarrage du service Proton Drive."
|
msgstr "En attente du démarrage du service Proton Drive."
|
||||||
|
|
||||||
@@ -479,7 +480,7 @@ msgstr "Rien dans le journal récent ne correspond à ce filtre."
|
|||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/prompt.rs:595
|
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/hud/mod.rs:245
|
||||||
msgid "Retry"
|
msgid "Retry"
|
||||||
msgstr "Réessayer"
|
msgstr "Réessayer"
|
||||||
|
|
||||||
@@ -1243,6 +1244,7 @@ msgstr "Le service Proton Drive n’a pas répondu."
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:265
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
msgstr "Ouvrir"
|
msgstr "Ouvrir"
|
||||||
@@ -1433,6 +1435,7 @@ msgstr "Nom"
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:133
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Size"
|
msgid "Size"
|
||||||
msgstr "Taille"
|
msgstr "Taille"
|
||||||
@@ -1609,6 +1612,7 @@ msgstr[1] "{n} fichiers sont désormais en ligne uniquement"
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:134
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Modified"
|
msgid "Modified"
|
||||||
msgstr "Modifié"
|
msgstr "Modifié"
|
||||||
@@ -1617,6 +1621,7 @@ msgstr "Modifié"
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:135
|
||||||
msgid "Location"
|
msgid "Location"
|
||||||
msgstr "Emplacement"
|
msgstr "Emplacement"
|
||||||
|
|
||||||
@@ -1635,6 +1640,7 @@ msgstr "Lire"
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:296
|
||||||
msgid "Available offline"
|
msgid "Available offline"
|
||||||
msgstr "Disponible hors ligne"
|
msgstr "Disponible hors ligne"
|
||||||
|
|
||||||
@@ -2056,7 +2062,6 @@ msgstr "Obtenir Proton Drive"
|
|||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
||||||
#: crates/pdfs-gui/src/prompt.rs:568
|
|
||||||
msgid "This computer"
|
msgid "This computer"
|
||||||
msgstr "Cet ordinateur"
|
msgstr "Cet ordinateur"
|
||||||
|
|
||||||
@@ -2546,7 +2551,7 @@ msgid "About Sync Modes"
|
|||||||
msgstr "À propos des modes de synchronisation"
|
msgstr "À propos des modes de synchronisation"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
||||||
#: crates/pdfs-gui/src/prompt.rs:60
|
#: crates/pdfs-gui/src/prompt.rs:65
|
||||||
msgid "Folders"
|
msgid "Folders"
|
||||||
msgstr "Dossiers"
|
msgstr "Dossiers"
|
||||||
|
|
||||||
@@ -3421,7 +3426,7 @@ msgid "Select Photo to Upload"
|
|||||||
msgstr "Sélectionner une photo à téléverser"
|
msgstr "Sélectionner une photo à téléverser"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
||||||
#: crates/pdfs-gui/src/prompt.rs:62
|
#: crates/pdfs-gui/src/prompt.rs:67
|
||||||
msgid "Images"
|
msgid "Images"
|
||||||
msgstr "Images"
|
msgstr "Images"
|
||||||
|
|
||||||
@@ -4598,7 +4603,7 @@ msgid "Up to date"
|
|||||||
msgstr "À jour"
|
msgstr "À jour"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
||||||
#: crates/pdfs-gui/src/prompt.rs:880 crates/pdfs-gui/src/prompt.rs:893
|
#: crates/pdfs-gui/src/hud/mod.rs:602 crates/pdfs-gui/src/hud/mod.rs:616
|
||||||
msgid "Proton Drive isn't running"
|
msgid "Proton Drive isn't running"
|
||||||
msgstr "Proton Drive n’est pas en cours d’exécution"
|
msgstr "Proton Drive n’est pas en cours d’exécution"
|
||||||
|
|
||||||
@@ -5143,6 +5148,7 @@ msgid "Proton Drive"
|
|||||||
msgstr "Proton Drive"
|
msgstr "Proton Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:263
|
||||||
msgid "Open folder"
|
msgid "Open folder"
|
||||||
msgstr "Ouvrir le dossier"
|
msgstr "Ouvrir le dossier"
|
||||||
|
|
||||||
@@ -5527,6 +5533,138 @@ msgstr "(aucun fichier épinglé — tapez pour rechercher)"
|
|||||||
msgid "(cannot resolve app dirs)"
|
msgid "(cannot resolve app dirs)"
|
||||||
msgstr "(impossible de déterminer les répertoires de l’application)"
|
msgstr "(impossible de déterminer les répertoires de l’application)"
|
||||||
|
|
||||||
|
#. Translators: the launcher's section of recently opened files.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:57
|
||||||
|
msgid "Recent"
|
||||||
|
msgstr "Récents"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:58
|
||||||
|
msgid "Pinned in Proton Drive"
|
||||||
|
msgstr "Épinglé dans Proton Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:59
|
||||||
|
msgid "Best matches"
|
||||||
|
msgstr "Meilleurs résultats"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:159
|
||||||
|
msgid "Search Proton Drive"
|
||||||
|
msgstr "Rechercher dans Proton Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:180
|
||||||
|
msgid "Search in Drive and on this computer"
|
||||||
|
msgstr "Rechercher dans Drive et sur cet ordinateur"
|
||||||
|
|
||||||
|
#. Translators: the Escape key, shown as a hint that it closes the window.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:191
|
||||||
|
msgid "Esc"
|
||||||
|
msgstr "Échap"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:232 crates/pdfs-gui/src/hud/mod.rs:862
|
||||||
|
msgid "No results"
|
||||||
|
msgstr "Aucun résultat"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:240
|
||||||
|
msgid "Proton Drive is not running"
|
||||||
|
msgstr "Proton Drive n’est pas en cours d’exécution"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:241
|
||||||
|
msgid "Start Proton Drive to search your files."
|
||||||
|
msgstr "Démarrez Proton Drive pour rechercher dans vos fichiers."
|
||||||
|
|
||||||
|
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:282
|
||||||
|
msgid "↑↓ navigate · ↵ open · Tab filter"
|
||||||
|
msgstr "↑↓ naviguer · ↵ ouvrir · Tab filtrer"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:864
|
||||||
|
msgid "Search your Drive"
|
||||||
|
msgstr "Rechercher dans votre Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:869
|
||||||
|
msgid "Still indexing this computer — local results will fill in shortly."
|
||||||
|
msgstr ""
|
||||||
|
"Indexation de cet ordinateur en cours — les résultats locaux vont bientôt "
|
||||||
|
"apparaître."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:871
|
||||||
|
msgid "Try a different search, or another filter."
|
||||||
|
msgstr "Essayez une autre recherche ou un autre filtre."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:873
|
||||||
|
msgid "Start typing to search Proton Drive and the files on this computer."
|
||||||
|
msgstr ""
|
||||||
|
"Commencez à taper pour rechercher dans Proton Drive et dans les fichiers de "
|
||||||
|
"cet ordinateur."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:900
|
||||||
|
msgid "No pinned files"
|
||||||
|
msgstr "Aucun fichier épinglé"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:901
|
||||||
|
#, rust-format
|
||||||
|
msgid "{n} pinned file"
|
||||||
|
msgid_plural "{n} pinned files"
|
||||||
|
msgstr[0] "{n} fichier épinglé"
|
||||||
|
msgstr[1] "{n} fichiers épinglés"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:914
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
||||||
|
msgstr "{drive} dans Drive · {local} sur cet ordinateur · indexation…"
|
||||||
|
|
||||||
|
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:919
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer"
|
||||||
|
msgstr "{drive} dans Drive · {local} sur cet ordinateur"
|
||||||
|
|
||||||
|
#. Translators: {name} is a file name.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1050
|
||||||
|
#, rust-format
|
||||||
|
msgid "Opening {name}…"
|
||||||
|
msgstr "Ouverture de {name}…"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1090
|
||||||
|
#, rust-format
|
||||||
|
msgid "Could not open: {message}"
|
||||||
|
msgstr "Impossible d’ouvrir : {message}"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1094
|
||||||
|
msgid "Couldn't reach Proton Drive"
|
||||||
|
msgstr "Impossible de joindre Proton Drive"
|
||||||
|
|
||||||
|
#. Translators: a heading in the launcher's preview pane, followed by
|
||||||
|
#. "Available offline", "Downloaded", "Online only" or "On this computer".
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:138
|
||||||
|
msgid "Availability"
|
||||||
|
msgstr "Disponibilité"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:157
|
||||||
|
msgid "Show in Folder"
|
||||||
|
msgstr "Afficher dans le dossier"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:160
|
||||||
|
msgid "Copy Path"
|
||||||
|
msgstr "Copier le chemin"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:166
|
||||||
|
msgid "Path copied"
|
||||||
|
msgstr "Chemin copié"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:291
|
||||||
|
msgid "On this computer"
|
||||||
|
msgstr "Sur cet ordinateur"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:303
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Downloaded"
|
||||||
|
msgstr "Téléchargé"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:308
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Online only"
|
||||||
|
msgstr "En ligne uniquement"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/i18n.rs:84
|
#: crates/pdfs-gui/src/i18n.rs:84
|
||||||
msgid "Kosovo"
|
msgid "Kosovo"
|
||||||
msgstr "Kosovo"
|
msgstr "Kosovo"
|
||||||
@@ -5680,16 +5818,16 @@ msgid "Quit"
|
|||||||
msgstr "Quitter"
|
msgstr "Quitter"
|
||||||
|
|
||||||
#. Translators: a filter chip that shows every kind of result.
|
#. Translators: a filter chip that shows every kind of result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:59
|
#: crates/pdfs-gui/src/prompt.rs:64
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Tout"
|
msgstr "Tout"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:61
|
#: crates/pdfs-gui/src/prompt.rs:66
|
||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Documents"
|
msgstr "Documents"
|
||||||
|
|
||||||
#. Translators: a filter chip for audio and video files.
|
#. Translators: a filter chip for audio and video files.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:64
|
#: crates/pdfs-gui/src/prompt.rs:69
|
||||||
msgid "Media"
|
msgid "Media"
|
||||||
msgstr "Médias"
|
msgstr "Médias"
|
||||||
|
|
||||||
@@ -5698,144 +5836,49 @@ msgstr "Médias"
|
|||||||
msgid "My files / {path}"
|
msgid "My files / {path}"
|
||||||
msgstr "Mes fichiers / {path}"
|
msgstr "Mes fichiers / {path}"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:520
|
|
||||||
msgid "Search Proton Drive"
|
|
||||||
msgstr "Rechercher dans Proton Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:541
|
|
||||||
msgid "Search in Drive and on this computer"
|
|
||||||
msgstr "Rechercher dans Drive et sur cet ordinateur"
|
|
||||||
|
|
||||||
#. Translators: the Escape key, shown as a hint that it closes the window.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:552
|
|
||||||
msgid "Esc"
|
|
||||||
msgstr "Échap"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:566 crates/pdfs-gui/src/prompt.rs:1085
|
|
||||||
msgid "Best matches"
|
|
||||||
msgstr "Meilleurs résultats"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:582 crates/pdfs-gui/src/prompt.rs:1109
|
|
||||||
msgid "No results"
|
|
||||||
msgstr "Aucun résultat"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:590
|
|
||||||
msgid "Proton Drive is not running"
|
|
||||||
msgstr "Proton Drive n’est pas en cours d’exécution"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:591
|
|
||||||
msgid "Start Proton Drive to search your files."
|
|
||||||
msgstr "Démarrez Proton Drive pour rechercher dans vos fichiers."
|
|
||||||
|
|
||||||
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:624
|
|
||||||
msgid "↑↓ navigate · ↵ open · Tab filter"
|
|
||||||
msgstr "↑↓ naviguer · ↵ ouvrir · Tab filtrer"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1096
|
|
||||||
msgid "Pinned in Proton Drive"
|
|
||||||
msgstr "Épinglé dans Proton Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1111
|
|
||||||
msgid "Search your Drive"
|
|
||||||
msgstr "Rechercher dans votre Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1116
|
|
||||||
msgid "Still indexing this computer — local results will fill in shortly."
|
|
||||||
msgstr ""
|
|
||||||
"Indexation de cet ordinateur en cours — les résultats locaux vont bientôt "
|
|
||||||
"apparaître."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1118
|
|
||||||
msgid "Try a different search, or another filter."
|
|
||||||
msgstr "Essayez une autre recherche ou un autre filtre."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1120
|
|
||||||
msgid "Start typing to search Proton Drive and the files on this computer."
|
|
||||||
msgstr ""
|
|
||||||
"Commencez à taper pour rechercher dans Proton Drive et dans les fichiers de "
|
|
||||||
"cet ordinateur."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1146
|
|
||||||
msgid "No pinned files"
|
|
||||||
msgstr "Aucun fichier épinglé"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1147
|
|
||||||
#, rust-format
|
|
||||||
msgid "{n} pinned file"
|
|
||||||
msgid_plural "{n} pinned files"
|
|
||||||
msgstr[0] "{n} fichier épinglé"
|
|
||||||
msgstr[1] "{n} fichiers épinglés"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1156
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
|
||||||
msgstr "{drive} dans Drive · {local} sur cet ordinateur · indexation…"
|
|
||||||
|
|
||||||
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1161
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer"
|
|
||||||
msgstr "{drive} dans Drive · {local} sur cet ordinateur"
|
|
||||||
|
|
||||||
#. Translators: {name} is a file name.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1294
|
|
||||||
#, rust-format
|
|
||||||
msgid "Opening {name}…"
|
|
||||||
msgstr "Ouverture de {name}…"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1317
|
|
||||||
#, rust-format
|
|
||||||
msgid "Could not open: {message}"
|
|
||||||
msgstr "Impossible d’ouvrir : {message}"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1321
|
|
||||||
msgid "Couldn't reach Proton Drive"
|
|
||||||
msgstr "Impossible de joindre Proton Drive"
|
|
||||||
|
|
||||||
#. Translators: the user's home folder, as the location of a result.
|
#. Translators: the user's home folder, as the location of a result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1430
|
#: crates/pdfs-gui/src/prompt.rs:455
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Dossier personnel"
|
msgstr "Dossier personnel"
|
||||||
|
|
||||||
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1432
|
#: crates/pdfs-gui/src/prompt.rs:457
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "Home / {path}"
|
msgid "Home / {path}"
|
||||||
msgstr "Dossier personnel / {path}"
|
msgstr "Dossier personnel / {path}"
|
||||||
|
|
||||||
#. Translators: the age of a file modified under a minute ago.
|
#. Translators: the age of a file modified under a minute ago.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1452
|
#: crates/pdfs-gui/src/prompt.rs:477
|
||||||
msgctxt "age"
|
msgctxt "age"
|
||||||
msgid "now"
|
msgid "now"
|
||||||
msgstr "maintenant"
|
msgstr "maintenant"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of minutes.
|
#. Translators: a compact file age; {count} is a number of minutes.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1454
|
#: crates/pdfs-gui/src/prompt.rs:479
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}m"
|
msgid "{count}m"
|
||||||
msgstr "{count} min"
|
msgstr "{count} min"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of hours.
|
#. Translators: a compact file age; {count} is a number of hours.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1456
|
#: crates/pdfs-gui/src/prompt.rs:481
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}h"
|
msgid "{count}h"
|
||||||
msgstr "{count} h"
|
msgstr "{count} h"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of days.
|
#. Translators: a compact file age; {count} is a number of days.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1458
|
#: crates/pdfs-gui/src/prompt.rs:483
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}d"
|
msgid "{count}d"
|
||||||
msgstr "{count} j"
|
msgstr "{count} j"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of months.
|
#. Translators: a compact file age; {count} is a number of months.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1460
|
#: crates/pdfs-gui/src/prompt.rs:485
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}mo"
|
msgid "{count}mo"
|
||||||
msgstr "{count} mois"
|
msgstr "{count} mois"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of years.
|
#. Translators: a compact file age; {count} is a number of years.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1462
|
#: crates/pdfs-gui/src/prompt.rs:487
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}y"
|
msgid "{count}y"
|
||||||
msgstr "{count} a"
|
msgstr "{count} a"
|
||||||
@@ -5986,9 +6029,6 @@ msgstr "impossible de joindre le démon Proton Drive : {error}"
|
|||||||
#~ msgid "Use this computer's identity"
|
#~ msgid "Use this computer's identity"
|
||||||
#~ msgstr "Utiliser l’identité de cet ordinateur"
|
#~ msgstr "Utiliser l’identité de cet ordinateur"
|
||||||
|
|
||||||
#~ msgid "On this computer"
|
|
||||||
#~ msgstr "Sur cet ordinateur"
|
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
#~ "Where Proton Drive lives on this machine. Synced folders keep a full copy "
|
#~ "Where Proton Drive lives on this machine. Synced folders keep a full copy "
|
||||||
#~ "on this disk; on-demand folders keep the files in Proton Drive only and "
|
#~ "on this disk; on-demand folders keep the files in Proton Drive only and "
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: proton-drive-linux 2.1.0\n"
|
"Project-Id-Version: proton-drive-linux 2.1.0\n"
|
||||||
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
||||||
"POT-Creation-Date: 2026-09-28 16:17+0200\n"
|
"POT-Creation-Date: 2026-09-28 22:51+0200\n"
|
||||||
"PO-Revision-Date: 2026-09-23 12:00+0200\n"
|
"PO-Revision-Date: 2026-09-23 12:00+0200\n"
|
||||||
"Last-Translator: Proton Drive for Linux contributors\n"
|
"Last-Translator: Proton Drive for Linux contributors\n"
|
||||||
"Language-Team: Italian\n"
|
"Language-Team: Italian\n"
|
||||||
@@ -57,7 +57,7 @@ msgstr "%-d %b %Y"
|
|||||||
msgid "%A, %B %-d, %Y, %H:%M"
|
msgid "%A, %B %-d, %Y, %H:%M"
|
||||||
msgstr "%A %-d %B %Y, %H:%M"
|
msgstr "%A %-d %B %Y, %H:%M"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:882
|
#: crates/pdfs-gui/src/app/main.rs:734
|
||||||
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
||||||
@@ -68,91 +68,91 @@ msgstr "%A %-d %B %Y, %H:%M"
|
|||||||
msgid "My files"
|
msgid "My files"
|
||||||
msgstr "I miei file"
|
msgstr "I miei file"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:885 crates/pdfs-gui/src/app/main.rs:1529
|
#: crates/pdfs-gui/src/app/main.rs:737 crates/pdfs-gui/src/app/main.rs:1381
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
||||||
msgid "Photos"
|
msgid "Photos"
|
||||||
msgstr "Foto"
|
msgstr "Foto"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:890
|
#: crates/pdfs-gui/src/app/main.rs:742
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
||||||
msgid "Shared with me"
|
msgid "Shared with me"
|
||||||
msgstr "Condivisi con me"
|
msgstr "Condivisi con me"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:895
|
#: crates/pdfs-gui/src/app/main.rs:747
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
||||||
msgid "Shared by me"
|
msgid "Shared by me"
|
||||||
msgstr "Condivisi da me"
|
msgstr "Condivisi da me"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:898
|
#: crates/pdfs-gui/src/app/main.rs:750
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
||||||
msgid "Computers"
|
msgid "Computers"
|
||||||
msgstr "Computer"
|
msgstr "Computer"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:899
|
#: crates/pdfs-gui/src/app/main.rs:751
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
||||||
msgid "Trash"
|
msgid "Trash"
|
||||||
msgstr "Cestino"
|
msgstr "Cestino"
|
||||||
|
|
||||||
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:901
|
#: crates/pdfs-gui/src/app/main.rs:753
|
||||||
msgid "Sync"
|
msgid "Sync"
|
||||||
msgstr "Sincronizzazione"
|
msgstr "Sincronizzazione"
|
||||||
|
|
||||||
#. Translators: a failed action's toast. {what} says what failed, such as
|
#. Translators: a failed action's toast. {what} says what failed, such as
|
||||||
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
||||||
#. from the mount service.
|
#. from the mount service.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1218
|
#: crates/pdfs-gui/src/app/main.rs:1070
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{what}: {detail}"
|
msgid "{what}: {detail}"
|
||||||
msgstr "{what}: {detail}"
|
msgstr "{what}: {detail}"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1237
|
#: crates/pdfs-gui/src/app/main.rs:1089
|
||||||
msgid "You're offline"
|
msgid "You're offline"
|
||||||
msgstr "Sei offline"
|
msgstr "Sei offline"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1238
|
#: crates/pdfs-gui/src/app/main.rs:1090
|
||||||
msgid "That's not there any more"
|
msgid "That's not there any more"
|
||||||
msgstr "Non esiste più"
|
msgstr "Non esiste più"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1239
|
#: crates/pdfs-gui/src/app/main.rs:1091
|
||||||
msgid "You don't have access to that"
|
msgid "You don't have access to that"
|
||||||
msgstr "Non hai accesso a questo elemento"
|
msgstr "Non hai accesso a questo elemento"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1240
|
#: crates/pdfs-gui/src/app/main.rs:1092
|
||||||
msgid "Something changed this first"
|
msgid "Something changed this first"
|
||||||
msgstr "È stato modificato prima da qualcos'altro"
|
msgstr "È stato modificato prima da qualcos'altro"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1241
|
#: crates/pdfs-gui/src/app/main.rs:1093
|
||||||
msgid "Your Proton Drive is full"
|
msgid "Your Proton Drive is full"
|
||||||
msgstr "Il tuo Proton Drive è pieno"
|
msgstr "Il tuo Proton Drive è pieno"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1282 crates/pdfs-gui/src/app/main.rs:1511
|
#: crates/pdfs-gui/src/app/main.rs:1134 crates/pdfs-gui/src/app/main.rs:1363
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
||||||
msgid "Preferences"
|
msgid "Preferences"
|
||||||
msgstr "Preferenze"
|
msgstr "Preferenze"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1283
|
#: crates/pdfs-gui/src/app/main.rs:1135
|
||||||
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
||||||
msgid "Keyboard Shortcuts"
|
msgid "Keyboard Shortcuts"
|
||||||
msgstr "Scorciatoie da tastiera"
|
msgstr "Scorciatoie da tastiera"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1285
|
#: crates/pdfs-gui/src/app/main.rs:1137
|
||||||
msgid "About Proton Drive for Linux"
|
msgid "About Proton Drive for Linux"
|
||||||
msgstr "Informazioni su Proton Drive for Linux"
|
msgstr "Informazioni su Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1290
|
#: crates/pdfs-gui/src/app/main.rs:1142
|
||||||
msgid "Main menu"
|
msgid "Main menu"
|
||||||
msgstr "Menu principale"
|
msgstr "Menu principale"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1346
|
#: crates/pdfs-gui/src/app/main.rs:1198
|
||||||
msgid "Proton Drive for Linux"
|
msgid "Proton Drive for Linux"
|
||||||
msgstr "Proton Drive for Linux"
|
msgstr "Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1354
|
#: crates/pdfs-gui/src/app/main.rs:1206
|
||||||
msgid ""
|
msgid ""
|
||||||
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -163,23 +163,23 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Client non ufficiale: non affiliato, approvato né supportato da Proton AG."
|
"Client non ufficiale: non affiliato, approvato né supportato da Proton AG."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1359
|
#: crates/pdfs-gui/src/app/main.rs:1211
|
||||||
msgid "Source Code"
|
msgid "Source Code"
|
||||||
msgstr "Codice sorgente"
|
msgstr "Codice sorgente"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1363
|
#: crates/pdfs-gui/src/app/main.rs:1215
|
||||||
msgid "Proton Terms of Service"
|
msgid "Proton Terms of Service"
|
||||||
msgstr "Termini di servizio di Proton"
|
msgstr "Termini di servizio di Proton"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1367
|
#: crates/pdfs-gui/src/app/main.rs:1219
|
||||||
msgid "Proton Privacy Policy"
|
msgid "Proton Privacy Policy"
|
||||||
msgstr "Informativa sulla privacy di Proton"
|
msgstr "Informativa sulla privacy di Proton"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1372
|
#: crates/pdfs-gui/src/app/main.rs:1224
|
||||||
msgid "Disclaimer"
|
msgid "Disclaimer"
|
||||||
msgstr "Esclusione di responsabilità"
|
msgstr "Esclusione di responsabilità"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1376
|
#: crates/pdfs-gui/src/app/main.rs:1228
|
||||||
msgid ""
|
msgid ""
|
||||||
"This is an unofficial, community-built client. It is not affiliated with, "
|
"This is an unofficial, community-built client. It is not affiliated with, "
|
||||||
"endorsed by, or supported by Proton AG.\n"
|
"endorsed by, or supported by Proton AG.\n"
|
||||||
@@ -202,7 +202,7 @@ msgstr ""
|
|||||||
"Questo software è fornito “così com’è”, senza alcuna garanzia. Conserva una "
|
"Questo software è fornito “così com’è”, senza alcuna garanzia. Conserva una "
|
||||||
"copia dei dati che non puoi permetterti di perdere."
|
"copia dei dati che non puoi permetterti di perdere."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1384
|
#: crates/pdfs-gui/src/app/main.rs:1236
|
||||||
msgid ""
|
msgid ""
|
||||||
"Places are named with data from <a href=\"https://www.geonames.org/"
|
"Places are named with data from <a href=\"https://www.geonames.org/"
|
||||||
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
||||||
@@ -212,7 +212,7 @@ msgstr ""
|
|||||||
"\">GeoNames</a>, con licenza <a href=\"https://creativecommons.org/licenses/"
|
"\">GeoNames</a>, con licenza <a href=\"https://creativecommons.org/licenses/"
|
||||||
"by/4.0/\">CC BY 4.0</a>."
|
"by/4.0/\">CC BY 4.0</a>."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1392
|
#: crates/pdfs-gui/src/app/main.rs:1244
|
||||||
msgid ""
|
msgid ""
|
||||||
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
||||||
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
||||||
@@ -222,7 +222,7 @@ msgstr ""
|
|||||||
"confini nazionali di <a href=\"https://www.naturalearthdata.com/\">Natural "
|
"confini nazionali di <a href=\"https://www.naturalearthdata.com/\">Natural "
|
||||||
"Earth</a>, che sono di pubblico dominio."
|
"Earth</a>, che sono di pubblico dominio."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1400
|
#: crates/pdfs-gui/src/app/main.rs:1252
|
||||||
msgid ""
|
msgid ""
|
||||||
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
||||||
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
||||||
@@ -238,15 +238,15 @@ msgstr ""
|
|||||||
|
|
||||||
#. Translators: replace with your name(s) and email address(es), one
|
#. Translators: replace with your name(s) and email address(es), one
|
||||||
#. per line, to be credited in the About dialog.
|
#. per line, to be credited in the About dialog.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1411
|
#: crates/pdfs-gui/src/app/main.rs:1263
|
||||||
msgid "translator-credits"
|
msgid "translator-credits"
|
||||||
msgstr "Collaboratori di Proton Drive for Linux"
|
msgstr "Collaboratori di Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1449
|
#: crates/pdfs-gui/src/app/main.rs:1301
|
||||||
msgid "Stop Proton Drive?"
|
msgid "Stop Proton Drive?"
|
||||||
msgstr "Arrestare Proton Drive?"
|
msgstr "Arrestare Proton Drive?"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1450
|
#: crates/pdfs-gui/src/app/main.rs:1302
|
||||||
msgid ""
|
msgid ""
|
||||||
"My files goes offline and nothing syncs until you connect again. Proton "
|
"My files goes offline and nothing syncs until you connect again. Proton "
|
||||||
"Drive starts again at your next login."
|
"Drive starts again at your next login."
|
||||||
@@ -254,7 +254,7 @@ msgstr ""
|
|||||||
"I miei file andranno offline e nulla verrà sincronizzato finché non ti "
|
"I miei file andranno offline e nulla verrà sincronizzato finché non ti "
|
||||||
"riconnetti. Proton Drive si riavvierà al prossimo accesso."
|
"riconnetti. Proton Drive si riavvierà al prossimo accesso."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1452 crates/pdfs-gui/src/app/main.rs:1727
|
#: crates/pdfs-gui/src/app/main.rs:1304 crates/pdfs-gui/src/app/main.rs:1579
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
||||||
@@ -283,30 +283,30 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Annulla"
|
msgstr "Annulla"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1453
|
#: crates/pdfs-gui/src/app/main.rs:1305
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Stop"
|
msgid "Stop"
|
||||||
msgstr "Arresta"
|
msgstr "Arresta"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1470
|
#: crates/pdfs-gui/src/app/main.rs:1322
|
||||||
msgid "Couldn't stop Proton Drive"
|
msgid "Couldn't stop Proton Drive"
|
||||||
msgstr "Impossibile arrestare Proton Drive"
|
msgstr "Impossibile arrestare Proton Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1471
|
#: crates/pdfs-gui/src/app/main.rs:1323
|
||||||
msgid "The Proton Drive service did not stop."
|
msgid "The Proton Drive service did not stop."
|
||||||
msgstr "Il servizio di Proton Drive non si è arrestato."
|
msgstr "Il servizio di Proton Drive non si è arrestato."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1507
|
#: crates/pdfs-gui/src/app/main.rs:1359
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
||||||
msgid "General"
|
msgid "General"
|
||||||
msgstr "Generale"
|
msgstr "Generale"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1509
|
#: crates/pdfs-gui/src/app/main.rs:1361
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
||||||
msgid "Search Drive"
|
msgid "Search Drive"
|
||||||
msgstr "Cerca in Drive"
|
msgstr "Cerca in Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1510
|
#: crates/pdfs-gui/src/app/main.rs:1362
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
||||||
@@ -315,38 +315,38 @@ msgstr "Cerca in Drive"
|
|||||||
msgid "Refresh"
|
msgid "Refresh"
|
||||||
msgstr "Aggiorna"
|
msgstr "Aggiorna"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1512
|
#: crates/pdfs-gui/src/app/main.rs:1364
|
||||||
msgid "Keyboard shortcuts"
|
msgid "Keyboard shortcuts"
|
||||||
msgstr "Scorciatoie da tastiera"
|
msgstr "Scorciatoie da tastiera"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1516
|
#: crates/pdfs-gui/src/app/main.rs:1368
|
||||||
msgid "Files"
|
msgid "Files"
|
||||||
msgstr "File"
|
msgstr "File"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1518
|
#: crates/pdfs-gui/src/app/main.rs:1370
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
||||||
msgid "New folder"
|
msgid "New folder"
|
||||||
msgstr "Nuova cartella"
|
msgstr "Nuova cartella"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1519
|
#: crates/pdfs-gui/src/app/main.rs:1371
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
||||||
msgid "Upload files"
|
msgid "Upload files"
|
||||||
msgstr "Carica file"
|
msgstr "Carica file"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1520
|
#: crates/pdfs-gui/src/app/main.rs:1372
|
||||||
msgid "Back / forward"
|
msgid "Back / forward"
|
||||||
msgstr "Indietro / avanti"
|
msgstr "Indietro / avanti"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1521
|
#: crates/pdfs-gui/src/app/main.rs:1373
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
||||||
msgid "Parent folder"
|
msgid "Parent folder"
|
||||||
msgstr "Cartella superiore"
|
msgstr "Cartella superiore"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1522
|
#: crates/pdfs-gui/src/app/main.rs:1374
|
||||||
msgid "Grid / list"
|
msgid "Grid / list"
|
||||||
msgstr "Griglia / elenco"
|
msgstr "Griglia / elenco"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1523
|
#: crates/pdfs-gui/src/app/main.rs:1375
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
||||||
@@ -355,7 +355,7 @@ msgstr "Griglia / elenco"
|
|||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
msgstr "Rinomina"
|
msgstr "Rinomina"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1524 crates/pdfs-gui/src/app/main.rs:1544
|
#: crates/pdfs-gui/src/app/main.rs:1376 crates/pdfs-gui/src/app/main.rs:1396
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
||||||
@@ -365,72 +365,73 @@ msgstr "Rinomina"
|
|||||||
msgid "Move to Trash"
|
msgid "Move to Trash"
|
||||||
msgstr "Sposta nel cestino"
|
msgstr "Sposta nel cestino"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1525
|
#: crates/pdfs-gui/src/app/main.rs:1377
|
||||||
msgid "Clear the selection"
|
msgid "Clear the selection"
|
||||||
msgstr "Annulla la selezione"
|
msgstr "Annulla la selezione"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1531
|
#: crates/pdfs-gui/src/app/main.rs:1383
|
||||||
msgid "Larger thumbnails"
|
msgid "Larger thumbnails"
|
||||||
msgstr "Miniature più grandi"
|
msgstr "Miniature più grandi"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1532
|
#: crates/pdfs-gui/src/app/main.rs:1384
|
||||||
msgid "Smaller thumbnails"
|
msgid "Smaller thumbnails"
|
||||||
msgstr "Miniature più piccole"
|
msgstr "Miniature più piccole"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1533
|
#: crates/pdfs-gui/src/app/main.rs:1385
|
||||||
msgid "Reset thumbnail size"
|
msgid "Reset thumbnail size"
|
||||||
msgstr "Ripristina la dimensione delle miniature"
|
msgstr "Ripristina la dimensione delle miniature"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1534
|
#: crates/pdfs-gui/src/app/main.rs:1386
|
||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Seleziona tutto"
|
msgstr "Seleziona tutto"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1538
|
#: crates/pdfs-gui/src/app/main.rs:1390
|
||||||
msgid "Photo Viewer"
|
msgid "Photo Viewer"
|
||||||
msgstr "Visualizzatore di foto"
|
msgstr "Visualizzatore di foto"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1540
|
#: crates/pdfs-gui/src/app/main.rs:1392
|
||||||
msgid "Previous / next photo"
|
msgid "Previous / next photo"
|
||||||
msgstr "Foto precedente / successiva"
|
msgstr "Foto precedente / successiva"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1541
|
#: crates/pdfs-gui/src/app/main.rs:1393
|
||||||
msgid "First / last photo"
|
msgid "First / last photo"
|
||||||
msgstr "Prima / ultima foto"
|
msgstr "Prima / ultima foto"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1542
|
#: crates/pdfs-gui/src/app/main.rs:1394
|
||||||
msgid "Show details"
|
msgid "Show details"
|
||||||
msgstr "Mostra dettagli"
|
msgstr "Mostra dettagli"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1543
|
#: crates/pdfs-gui/src/app/main.rs:1395
|
||||||
msgid "Fullscreen"
|
msgid "Fullscreen"
|
||||||
msgstr "Schermo intero"
|
msgstr "Schermo intero"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1545
|
#: crates/pdfs-gui/src/app/main.rs:1397
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "Chiudi"
|
msgstr "Chiudi"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1635
|
#: crates/pdfs-gui/src/app/main.rs:1487
|
||||||
msgid "Select a single item to rename it"
|
msgid "Select a single item to rename it"
|
||||||
msgstr "Seleziona un solo elemento per rinominarlo"
|
msgstr "Seleziona un solo elemento per rinominarlo"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1795
|
#: crates/pdfs-gui/src/app/main.rs:1647
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
||||||
msgid "Not connected"
|
msgid "Not connected"
|
||||||
msgstr "Non connesso"
|
msgstr "Non connesso"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1796
|
#: crates/pdfs-gui/src/app/main.rs:1648
|
||||||
msgid "The Proton Drive service isn't running."
|
msgid "The Proton Drive service isn't running."
|
||||||
msgstr "Il servizio di Proton Drive non è in esecuzione."
|
msgstr "Il servizio di Proton Drive non è in esecuzione."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1804
|
#: crates/pdfs-gui/src/app/main.rs:1656
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:336 crates/pdfs-gui/src/main.rs:444
|
#: crates/pdfs-gui/src/app/pages/status.rs:336
|
||||||
#: crates/pdfs-gui/src/prompt.rs:616 crates/pdfs-gui/src/prompt.rs:867
|
#: crates/pdfs-gui/src/hud/mod.rs:274 crates/pdfs-gui/src/hud/mod.rs:589
|
||||||
|
#: crates/pdfs-gui/src/main.rs:444
|
||||||
msgid "Connecting…"
|
msgid "Connecting…"
|
||||||
msgstr "Connessione…"
|
msgstr "Connessione…"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1805
|
#: crates/pdfs-gui/src/app/main.rs:1657
|
||||||
msgid "Waiting for the Proton Drive service to start."
|
msgid "Waiting for the Proton Drive service to start."
|
||||||
msgstr "In attesa dell’avvio del servizio di Proton Drive."
|
msgstr "In attesa dell’avvio del servizio di Proton Drive."
|
||||||
|
|
||||||
@@ -475,7 +476,7 @@ msgstr "Nessuna voce del registro recente corrisponde a questo filtro."
|
|||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/prompt.rs:595
|
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/hud/mod.rs:245
|
||||||
msgid "Retry"
|
msgid "Retry"
|
||||||
msgstr "Riprova"
|
msgstr "Riprova"
|
||||||
|
|
||||||
@@ -1237,6 +1238,7 @@ msgstr "Il servizio di Proton Drive non ha risposto."
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:265
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
msgstr "Apri"
|
msgstr "Apri"
|
||||||
@@ -1427,6 +1429,7 @@ msgstr "Nome"
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:133
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Size"
|
msgid "Size"
|
||||||
msgstr "Dimensione"
|
msgstr "Dimensione"
|
||||||
@@ -1603,6 +1606,7 @@ msgstr[1] "{n} file sono ora solo online"
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:134
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Modified"
|
msgid "Modified"
|
||||||
msgstr "Modificato"
|
msgstr "Modificato"
|
||||||
@@ -1611,6 +1615,7 @@ msgstr "Modificato"
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:135
|
||||||
msgid "Location"
|
msgid "Location"
|
||||||
msgstr "Posizione"
|
msgstr "Posizione"
|
||||||
|
|
||||||
@@ -1629,6 +1634,7 @@ msgstr "Riproduci"
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:296
|
||||||
msgid "Available offline"
|
msgid "Available offline"
|
||||||
msgstr "Disponibile offline"
|
msgstr "Disponibile offline"
|
||||||
|
|
||||||
@@ -2050,7 +2056,6 @@ msgstr "Scarica Proton Drive"
|
|||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
||||||
#: crates/pdfs-gui/src/prompt.rs:568
|
|
||||||
msgid "This computer"
|
msgid "This computer"
|
||||||
msgstr "Questo computer"
|
msgstr "Questo computer"
|
||||||
|
|
||||||
@@ -2544,7 +2549,7 @@ msgid "About Sync Modes"
|
|||||||
msgstr "Informazioni sulle modalità di sincronizzazione"
|
msgstr "Informazioni sulle modalità di sincronizzazione"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
||||||
#: crates/pdfs-gui/src/prompt.rs:60
|
#: crates/pdfs-gui/src/prompt.rs:65
|
||||||
msgid "Folders"
|
msgid "Folders"
|
||||||
msgstr "Cartelle"
|
msgstr "Cartelle"
|
||||||
|
|
||||||
@@ -3417,7 +3422,7 @@ msgid "Select Photo to Upload"
|
|||||||
msgstr "Seleziona la foto da caricare"
|
msgstr "Seleziona la foto da caricare"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
||||||
#: crates/pdfs-gui/src/prompt.rs:62
|
#: crates/pdfs-gui/src/prompt.rs:67
|
||||||
msgid "Images"
|
msgid "Images"
|
||||||
msgstr "Immagini"
|
msgstr "Immagini"
|
||||||
|
|
||||||
@@ -4579,7 +4584,7 @@ msgid "Up to date"
|
|||||||
msgstr "Aggiornato"
|
msgstr "Aggiornato"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
||||||
#: crates/pdfs-gui/src/prompt.rs:880 crates/pdfs-gui/src/prompt.rs:893
|
#: crates/pdfs-gui/src/hud/mod.rs:602 crates/pdfs-gui/src/hud/mod.rs:616
|
||||||
msgid "Proton Drive isn't running"
|
msgid "Proton Drive isn't running"
|
||||||
msgstr "Proton Drive non è in esecuzione"
|
msgstr "Proton Drive non è in esecuzione"
|
||||||
|
|
||||||
@@ -5126,6 +5131,7 @@ msgid "Proton Drive"
|
|||||||
msgstr "Proton Drive"
|
msgstr "Proton Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:263
|
||||||
msgid "Open folder"
|
msgid "Open folder"
|
||||||
msgstr "Apri cartella"
|
msgstr "Apri cartella"
|
||||||
|
|
||||||
@@ -5508,6 +5514,137 @@ msgstr "(nessun file fissato: digita per cercare)"
|
|||||||
msgid "(cannot resolve app dirs)"
|
msgid "(cannot resolve app dirs)"
|
||||||
msgstr "(impossibile determinare le directory dell'app)"
|
msgstr "(impossibile determinare le directory dell'app)"
|
||||||
|
|
||||||
|
#. Translators: the launcher's section of recently opened files.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:57
|
||||||
|
msgid "Recent"
|
||||||
|
msgstr "Recenti"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:58
|
||||||
|
msgid "Pinned in Proton Drive"
|
||||||
|
msgstr "Fissato in Proton Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:59
|
||||||
|
msgid "Best matches"
|
||||||
|
msgstr "Migliori corrispondenze"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:159
|
||||||
|
msgid "Search Proton Drive"
|
||||||
|
msgstr "Cerca in Proton Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:180
|
||||||
|
msgid "Search in Drive and on this computer"
|
||||||
|
msgstr "Cerca in Drive e su questo computer"
|
||||||
|
|
||||||
|
#. Translators: the Escape key, shown as a hint that it closes the window.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:191
|
||||||
|
msgid "Esc"
|
||||||
|
msgstr "Esc"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:232 crates/pdfs-gui/src/hud/mod.rs:862
|
||||||
|
msgid "No results"
|
||||||
|
msgstr "Nessun risultato"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:240
|
||||||
|
msgid "Proton Drive is not running"
|
||||||
|
msgstr "Proton Drive non è in esecuzione"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:241
|
||||||
|
msgid "Start Proton Drive to search your files."
|
||||||
|
msgstr "Avvia Proton Drive per cercare nei tuoi file."
|
||||||
|
|
||||||
|
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:282
|
||||||
|
msgid "↑↓ navigate · ↵ open · Tab filter"
|
||||||
|
msgstr "↑↓ naviga · ↵ apri · Tab filtra"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:864
|
||||||
|
msgid "Search your Drive"
|
||||||
|
msgstr "Cerca nel tuo Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:869
|
||||||
|
msgid "Still indexing this computer — local results will fill in shortly."
|
||||||
|
msgstr ""
|
||||||
|
"Indicizzazione di questo computer ancora in corso: i risultati locali "
|
||||||
|
"arriveranno a breve."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:871
|
||||||
|
msgid "Try a different search, or another filter."
|
||||||
|
msgstr "Prova una ricerca diversa o un altro filtro."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:873
|
||||||
|
msgid "Start typing to search Proton Drive and the files on this computer."
|
||||||
|
msgstr ""
|
||||||
|
"Inizia a digitare per cercare in Proton Drive e nei file di questo computer."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:900
|
||||||
|
msgid "No pinned files"
|
||||||
|
msgstr "Nessun file fissato"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:901
|
||||||
|
#, rust-format
|
||||||
|
msgid "{n} pinned file"
|
||||||
|
msgid_plural "{n} pinned files"
|
||||||
|
msgstr[0] "{n} file fissato"
|
||||||
|
msgstr[1] "{n} file fissati"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:914
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
||||||
|
msgstr "{drive} in Drive · {local} su questo computer · indicizzazione…"
|
||||||
|
|
||||||
|
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:919
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer"
|
||||||
|
msgstr "{drive} in Drive · {local} su questo computer"
|
||||||
|
|
||||||
|
#. Translators: {name} is a file name.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1050
|
||||||
|
#, rust-format
|
||||||
|
msgid "Opening {name}…"
|
||||||
|
msgstr "Apertura di {name}…"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1090
|
||||||
|
#, rust-format
|
||||||
|
msgid "Could not open: {message}"
|
||||||
|
msgstr "Impossibile aprire: {message}"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1094
|
||||||
|
msgid "Couldn't reach Proton Drive"
|
||||||
|
msgstr "Impossibile raggiungere Proton Drive"
|
||||||
|
|
||||||
|
#. Translators: a heading in the launcher's preview pane, followed by
|
||||||
|
#. "Available offline", "Downloaded", "Online only" or "On this computer".
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:138
|
||||||
|
msgid "Availability"
|
||||||
|
msgstr "Disponibilità"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:157
|
||||||
|
msgid "Show in Folder"
|
||||||
|
msgstr "Mostra nella cartella"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:160
|
||||||
|
msgid "Copy Path"
|
||||||
|
msgstr "Copia percorso"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:166
|
||||||
|
msgid "Path copied"
|
||||||
|
msgstr "Percorso copiato"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:291
|
||||||
|
msgid "On this computer"
|
||||||
|
msgstr "Su questo computer"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:303
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Downloaded"
|
||||||
|
msgstr "Scaricato"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:308
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Online only"
|
||||||
|
msgstr "Solo online"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/i18n.rs:84
|
#: crates/pdfs-gui/src/i18n.rs:84
|
||||||
msgid "Kosovo"
|
msgid "Kosovo"
|
||||||
msgstr "Kosovo"
|
msgstr "Kosovo"
|
||||||
@@ -5661,16 +5798,16 @@ msgid "Quit"
|
|||||||
msgstr "Esci"
|
msgstr "Esci"
|
||||||
|
|
||||||
#. Translators: a filter chip that shows every kind of result.
|
#. Translators: a filter chip that shows every kind of result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:59
|
#: crates/pdfs-gui/src/prompt.rs:64
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Tutti"
|
msgstr "Tutti"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:61
|
#: crates/pdfs-gui/src/prompt.rs:66
|
||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Documenti"
|
msgstr "Documenti"
|
||||||
|
|
||||||
#. Translators: a filter chip for audio and video files.
|
#. Translators: a filter chip for audio and video files.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:64
|
#: crates/pdfs-gui/src/prompt.rs:69
|
||||||
msgid "Media"
|
msgid "Media"
|
||||||
msgstr "Multimedia"
|
msgstr "Multimedia"
|
||||||
|
|
||||||
@@ -5679,143 +5816,49 @@ msgstr "Multimedia"
|
|||||||
msgid "My files / {path}"
|
msgid "My files / {path}"
|
||||||
msgstr "I miei file / {path}"
|
msgstr "I miei file / {path}"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:520
|
|
||||||
msgid "Search Proton Drive"
|
|
||||||
msgstr "Cerca in Proton Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:541
|
|
||||||
msgid "Search in Drive and on this computer"
|
|
||||||
msgstr "Cerca in Drive e su questo computer"
|
|
||||||
|
|
||||||
#. Translators: the Escape key, shown as a hint that it closes the window.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:552
|
|
||||||
msgid "Esc"
|
|
||||||
msgstr "Esc"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:566 crates/pdfs-gui/src/prompt.rs:1085
|
|
||||||
msgid "Best matches"
|
|
||||||
msgstr "Migliori corrispondenze"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:582 crates/pdfs-gui/src/prompt.rs:1109
|
|
||||||
msgid "No results"
|
|
||||||
msgstr "Nessun risultato"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:590
|
|
||||||
msgid "Proton Drive is not running"
|
|
||||||
msgstr "Proton Drive non è in esecuzione"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:591
|
|
||||||
msgid "Start Proton Drive to search your files."
|
|
||||||
msgstr "Avvia Proton Drive per cercare nei tuoi file."
|
|
||||||
|
|
||||||
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:624
|
|
||||||
msgid "↑↓ navigate · ↵ open · Tab filter"
|
|
||||||
msgstr "↑↓ naviga · ↵ apri · Tab filtra"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1096
|
|
||||||
msgid "Pinned in Proton Drive"
|
|
||||||
msgstr "Fissato in Proton Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1111
|
|
||||||
msgid "Search your Drive"
|
|
||||||
msgstr "Cerca nel tuo Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1116
|
|
||||||
msgid "Still indexing this computer — local results will fill in shortly."
|
|
||||||
msgstr ""
|
|
||||||
"Indicizzazione di questo computer ancora in corso: i risultati locali "
|
|
||||||
"arriveranno a breve."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1118
|
|
||||||
msgid "Try a different search, or another filter."
|
|
||||||
msgstr "Prova una ricerca diversa o un altro filtro."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1120
|
|
||||||
msgid "Start typing to search Proton Drive and the files on this computer."
|
|
||||||
msgstr ""
|
|
||||||
"Inizia a digitare per cercare in Proton Drive e nei file di questo computer."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1146
|
|
||||||
msgid "No pinned files"
|
|
||||||
msgstr "Nessun file fissato"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1147
|
|
||||||
#, rust-format
|
|
||||||
msgid "{n} pinned file"
|
|
||||||
msgid_plural "{n} pinned files"
|
|
||||||
msgstr[0] "{n} file fissato"
|
|
||||||
msgstr[1] "{n} file fissati"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1156
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
|
||||||
msgstr "{drive} in Drive · {local} su questo computer · indicizzazione…"
|
|
||||||
|
|
||||||
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1161
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer"
|
|
||||||
msgstr "{drive} in Drive · {local} su questo computer"
|
|
||||||
|
|
||||||
#. Translators: {name} is a file name.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1294
|
|
||||||
#, rust-format
|
|
||||||
msgid "Opening {name}…"
|
|
||||||
msgstr "Apertura di {name}…"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1317
|
|
||||||
#, rust-format
|
|
||||||
msgid "Could not open: {message}"
|
|
||||||
msgstr "Impossibile aprire: {message}"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1321
|
|
||||||
msgid "Couldn't reach Proton Drive"
|
|
||||||
msgstr "Impossibile raggiungere Proton Drive"
|
|
||||||
|
|
||||||
#. Translators: the user's home folder, as the location of a result.
|
#. Translators: the user's home folder, as the location of a result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1430
|
#: crates/pdfs-gui/src/prompt.rs:455
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Home"
|
msgstr "Home"
|
||||||
|
|
||||||
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1432
|
#: crates/pdfs-gui/src/prompt.rs:457
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "Home / {path}"
|
msgid "Home / {path}"
|
||||||
msgstr "Home / {path}"
|
msgstr "Home / {path}"
|
||||||
|
|
||||||
#. Translators: the age of a file modified under a minute ago.
|
#. Translators: the age of a file modified under a minute ago.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1452
|
#: crates/pdfs-gui/src/prompt.rs:477
|
||||||
msgctxt "age"
|
msgctxt "age"
|
||||||
msgid "now"
|
msgid "now"
|
||||||
msgstr "ora"
|
msgstr "ora"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of minutes.
|
#. Translators: a compact file age; {count} is a number of minutes.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1454
|
#: crates/pdfs-gui/src/prompt.rs:479
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}m"
|
msgid "{count}m"
|
||||||
msgstr "{count} min"
|
msgstr "{count} min"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of hours.
|
#. Translators: a compact file age; {count} is a number of hours.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1456
|
#: crates/pdfs-gui/src/prompt.rs:481
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}h"
|
msgid "{count}h"
|
||||||
msgstr "{count} h"
|
msgstr "{count} h"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of days.
|
#. Translators: a compact file age; {count} is a number of days.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1458
|
#: crates/pdfs-gui/src/prompt.rs:483
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}d"
|
msgid "{count}d"
|
||||||
msgstr "{count} g"
|
msgstr "{count} g"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of months.
|
#. Translators: a compact file age; {count} is a number of months.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1460
|
#: crates/pdfs-gui/src/prompt.rs:485
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}mo"
|
msgid "{count}mo"
|
||||||
msgstr "{count} mes."
|
msgstr "{count} mes."
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of years.
|
#. Translators: a compact file age; {count} is a number of years.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1462
|
#: crates/pdfs-gui/src/prompt.rs:487
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}y"
|
msgid "{count}y"
|
||||||
msgstr "{count} a"
|
msgstr "{count} a"
|
||||||
@@ -5964,9 +6007,6 @@ msgstr "impossibile raggiungere il demone di Proton Drive: {error}"
|
|||||||
#~ msgid "Use this computer's identity"
|
#~ msgid "Use this computer's identity"
|
||||||
#~ msgstr "Usa l'identità di questo computer"
|
#~ msgstr "Usa l'identità di questo computer"
|
||||||
|
|
||||||
#~ msgid "On this computer"
|
|
||||||
#~ msgstr "Su questo computer"
|
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
#~ "Where Proton Drive lives on this machine. Synced folders keep a full copy "
|
#~ "Where Proton Drive lives on this machine. Synced folders keep a full copy "
|
||||||
#~ "on this disk; on-demand folders keep the files in Proton Drive only and "
|
#~ "on this disk; on-demand folders keep the files in Proton Drive only and "
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: proton-drive-linux 2.1.0\n"
|
"Project-Id-Version: proton-drive-linux 2.1.0\n"
|
||||||
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
||||||
"POT-Creation-Date: 2026-09-28 16:17+0200\n"
|
"POT-Creation-Date: 2026-09-28 22:51+0200\n"
|
||||||
"PO-Revision-Date: 2026-09-23 12:00+0200\n"
|
"PO-Revision-Date: 2026-09-23 12:00+0200\n"
|
||||||
"Last-Translator: Proton Drive for Linux contributors\n"
|
"Last-Translator: Proton Drive for Linux contributors\n"
|
||||||
"Language-Team: Dutch\n"
|
"Language-Team: Dutch\n"
|
||||||
@@ -57,7 +57,7 @@ msgstr "%-d %b %Y"
|
|||||||
msgid "%A, %B %-d, %Y, %H:%M"
|
msgid "%A, %B %-d, %Y, %H:%M"
|
||||||
msgstr "%A %-d %B %Y, %H:%M"
|
msgstr "%A %-d %B %Y, %H:%M"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:882
|
#: crates/pdfs-gui/src/app/main.rs:734
|
||||||
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
||||||
@@ -68,91 +68,91 @@ msgstr "%A %-d %B %Y, %H:%M"
|
|||||||
msgid "My files"
|
msgid "My files"
|
||||||
msgstr "Mijn bestanden"
|
msgstr "Mijn bestanden"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:885 crates/pdfs-gui/src/app/main.rs:1529
|
#: crates/pdfs-gui/src/app/main.rs:737 crates/pdfs-gui/src/app/main.rs:1381
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
||||||
msgid "Photos"
|
msgid "Photos"
|
||||||
msgstr "Foto's"
|
msgstr "Foto's"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:890
|
#: crates/pdfs-gui/src/app/main.rs:742
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
||||||
msgid "Shared with me"
|
msgid "Shared with me"
|
||||||
msgstr "Gedeeld met mij"
|
msgstr "Gedeeld met mij"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:895
|
#: crates/pdfs-gui/src/app/main.rs:747
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
||||||
msgid "Shared by me"
|
msgid "Shared by me"
|
||||||
msgstr "Gedeeld door mij"
|
msgstr "Gedeeld door mij"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:898
|
#: crates/pdfs-gui/src/app/main.rs:750
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
||||||
msgid "Computers"
|
msgid "Computers"
|
||||||
msgstr "Computers"
|
msgstr "Computers"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:899
|
#: crates/pdfs-gui/src/app/main.rs:751
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
||||||
msgid "Trash"
|
msgid "Trash"
|
||||||
msgstr "Prullenbak"
|
msgstr "Prullenbak"
|
||||||
|
|
||||||
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:901
|
#: crates/pdfs-gui/src/app/main.rs:753
|
||||||
msgid "Sync"
|
msgid "Sync"
|
||||||
msgstr "Synchronisatie"
|
msgstr "Synchronisatie"
|
||||||
|
|
||||||
#. Translators: a failed action's toast. {what} says what failed, such as
|
#. Translators: a failed action's toast. {what} says what failed, such as
|
||||||
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
||||||
#. from the mount service.
|
#. from the mount service.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1218
|
#: crates/pdfs-gui/src/app/main.rs:1070
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{what}: {detail}"
|
msgid "{what}: {detail}"
|
||||||
msgstr "{what}: {detail}"
|
msgstr "{what}: {detail}"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1237
|
#: crates/pdfs-gui/src/app/main.rs:1089
|
||||||
msgid "You're offline"
|
msgid "You're offline"
|
||||||
msgstr "Je bent offline"
|
msgstr "Je bent offline"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1238
|
#: crates/pdfs-gui/src/app/main.rs:1090
|
||||||
msgid "That's not there any more"
|
msgid "That's not there any more"
|
||||||
msgstr "Dat bestaat niet meer"
|
msgstr "Dat bestaat niet meer"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1239
|
#: crates/pdfs-gui/src/app/main.rs:1091
|
||||||
msgid "You don't have access to that"
|
msgid "You don't have access to that"
|
||||||
msgstr "Je hebt daar geen toegang toe"
|
msgstr "Je hebt daar geen toegang toe"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1240
|
#: crates/pdfs-gui/src/app/main.rs:1092
|
||||||
msgid "Something changed this first"
|
msgid "Something changed this first"
|
||||||
msgstr "Er is eerst iets anders gewijzigd"
|
msgstr "Er is eerst iets anders gewijzigd"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1241
|
#: crates/pdfs-gui/src/app/main.rs:1093
|
||||||
msgid "Your Proton Drive is full"
|
msgid "Your Proton Drive is full"
|
||||||
msgstr "Je Proton Drive is vol"
|
msgstr "Je Proton Drive is vol"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1282 crates/pdfs-gui/src/app/main.rs:1511
|
#: crates/pdfs-gui/src/app/main.rs:1134 crates/pdfs-gui/src/app/main.rs:1363
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
||||||
msgid "Preferences"
|
msgid "Preferences"
|
||||||
msgstr "Voorkeuren"
|
msgstr "Voorkeuren"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1283
|
#: crates/pdfs-gui/src/app/main.rs:1135
|
||||||
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
||||||
msgid "Keyboard Shortcuts"
|
msgid "Keyboard Shortcuts"
|
||||||
msgstr "Sneltoetsen"
|
msgstr "Sneltoetsen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1285
|
#: crates/pdfs-gui/src/app/main.rs:1137
|
||||||
msgid "About Proton Drive for Linux"
|
msgid "About Proton Drive for Linux"
|
||||||
msgstr "Over Proton Drive for Linux"
|
msgstr "Over Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1290
|
#: crates/pdfs-gui/src/app/main.rs:1142
|
||||||
msgid "Main menu"
|
msgid "Main menu"
|
||||||
msgstr "Hoofdmenu"
|
msgstr "Hoofdmenu"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1346
|
#: crates/pdfs-gui/src/app/main.rs:1198
|
||||||
msgid "Proton Drive for Linux"
|
msgid "Proton Drive for Linux"
|
||||||
msgstr "Proton Drive for Linux"
|
msgstr "Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1354
|
#: crates/pdfs-gui/src/app/main.rs:1206
|
||||||
msgid ""
|
msgid ""
|
||||||
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -164,23 +164,23 @@ msgstr ""
|
|||||||
"Onofficiële client — niet gelieerd aan, onderschreven door of ondersteund "
|
"Onofficiële client — niet gelieerd aan, onderschreven door of ondersteund "
|
||||||
"door Proton AG."
|
"door Proton AG."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1359
|
#: crates/pdfs-gui/src/app/main.rs:1211
|
||||||
msgid "Source Code"
|
msgid "Source Code"
|
||||||
msgstr "Broncode"
|
msgstr "Broncode"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1363
|
#: crates/pdfs-gui/src/app/main.rs:1215
|
||||||
msgid "Proton Terms of Service"
|
msgid "Proton Terms of Service"
|
||||||
msgstr "Servicevoorwaarden van Proton"
|
msgstr "Servicevoorwaarden van Proton"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1367
|
#: crates/pdfs-gui/src/app/main.rs:1219
|
||||||
msgid "Proton Privacy Policy"
|
msgid "Proton Privacy Policy"
|
||||||
msgstr "Privacybeleid van Proton"
|
msgstr "Privacybeleid van Proton"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1372
|
#: crates/pdfs-gui/src/app/main.rs:1224
|
||||||
msgid "Disclaimer"
|
msgid "Disclaimer"
|
||||||
msgstr "Disclaimer"
|
msgstr "Disclaimer"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1376
|
#: crates/pdfs-gui/src/app/main.rs:1228
|
||||||
msgid ""
|
msgid ""
|
||||||
"This is an unofficial, community-built client. It is not affiliated with, "
|
"This is an unofficial, community-built client. It is not affiliated with, "
|
||||||
"endorsed by, or supported by Proton AG.\n"
|
"endorsed by, or supported by Proton AG.\n"
|
||||||
@@ -203,7 +203,7 @@ msgstr ""
|
|||||||
"Deze software wordt geleverd “zoals ze is”, zonder enige garantie. Bewaar "
|
"Deze software wordt geleverd “zoals ze is”, zonder enige garantie. Bewaar "
|
||||||
"een kopie van gegevens die je niet kwijt wilt raken."
|
"een kopie van gegevens die je niet kwijt wilt raken."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1384
|
#: crates/pdfs-gui/src/app/main.rs:1236
|
||||||
msgid ""
|
msgid ""
|
||||||
"Places are named with data from <a href=\"https://www.geonames.org/"
|
"Places are named with data from <a href=\"https://www.geonames.org/"
|
||||||
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
||||||
@@ -213,7 +213,7 @@ msgstr ""
|
|||||||
"\">GeoNames</a>, gelicentieerd onder <a href=\"https://creativecommons.org/"
|
"\">GeoNames</a>, gelicentieerd onder <a href=\"https://creativecommons.org/"
|
||||||
"licenses/by/4.0/\">CC BY 4.0</a>."
|
"licenses/by/4.0/\">CC BY 4.0</a>."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1392
|
#: crates/pdfs-gui/src/app/main.rs:1244
|
||||||
msgid ""
|
msgid ""
|
||||||
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
||||||
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
||||||
@@ -223,7 +223,7 @@ msgstr ""
|
|||||||
"href=\"https://www.naturalearthdata.com/\">Natural Earth</a>, die in het "
|
"href=\"https://www.naturalearthdata.com/\">Natural Earth</a>, die in het "
|
||||||
"publieke domein vallen."
|
"publieke domein vallen."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1400
|
#: crates/pdfs-gui/src/app/main.rs:1252
|
||||||
msgid ""
|
msgid ""
|
||||||
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
||||||
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
||||||
@@ -239,15 +239,15 @@ msgstr ""
|
|||||||
|
|
||||||
#. Translators: replace with your name(s) and email address(es), one
|
#. Translators: replace with your name(s) and email address(es), one
|
||||||
#. per line, to be credited in the About dialog.
|
#. per line, to be credited in the About dialog.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1411
|
#: crates/pdfs-gui/src/app/main.rs:1263
|
||||||
msgid "translator-credits"
|
msgid "translator-credits"
|
||||||
msgstr "Bijdragers aan Proton Drive for Linux"
|
msgstr "Bijdragers aan Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1449
|
#: crates/pdfs-gui/src/app/main.rs:1301
|
||||||
msgid "Stop Proton Drive?"
|
msgid "Stop Proton Drive?"
|
||||||
msgstr "Proton Drive stoppen?"
|
msgstr "Proton Drive stoppen?"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1450
|
#: crates/pdfs-gui/src/app/main.rs:1302
|
||||||
msgid ""
|
msgid ""
|
||||||
"My files goes offline and nothing syncs until you connect again. Proton "
|
"My files goes offline and nothing syncs until you connect again. Proton "
|
||||||
"Drive starts again at your next login."
|
"Drive starts again at your next login."
|
||||||
@@ -255,7 +255,7 @@ msgstr ""
|
|||||||
"Mijn bestanden gaat offline en er wordt niets gesynchroniseerd tot je weer "
|
"Mijn bestanden gaat offline en er wordt niets gesynchroniseerd tot je weer "
|
||||||
"verbinding maakt. Proton Drive start opnieuw bij je volgende aanmelding."
|
"verbinding maakt. Proton Drive start opnieuw bij je volgende aanmelding."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1452 crates/pdfs-gui/src/app/main.rs:1727
|
#: crates/pdfs-gui/src/app/main.rs:1304 crates/pdfs-gui/src/app/main.rs:1579
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
||||||
@@ -284,30 +284,30 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Annuleren"
|
msgstr "Annuleren"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1453
|
#: crates/pdfs-gui/src/app/main.rs:1305
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Stop"
|
msgid "Stop"
|
||||||
msgstr "Stoppen"
|
msgstr "Stoppen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1470
|
#: crates/pdfs-gui/src/app/main.rs:1322
|
||||||
msgid "Couldn't stop Proton Drive"
|
msgid "Couldn't stop Proton Drive"
|
||||||
msgstr "Kon Proton Drive niet stoppen"
|
msgstr "Kon Proton Drive niet stoppen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1471
|
#: crates/pdfs-gui/src/app/main.rs:1323
|
||||||
msgid "The Proton Drive service did not stop."
|
msgid "The Proton Drive service did not stop."
|
||||||
msgstr "De Proton Drive-service is niet gestopt."
|
msgstr "De Proton Drive-service is niet gestopt."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1507
|
#: crates/pdfs-gui/src/app/main.rs:1359
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
||||||
msgid "General"
|
msgid "General"
|
||||||
msgstr "Algemeen"
|
msgstr "Algemeen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1509
|
#: crates/pdfs-gui/src/app/main.rs:1361
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
||||||
msgid "Search Drive"
|
msgid "Search Drive"
|
||||||
msgstr "Drive doorzoeken"
|
msgstr "Drive doorzoeken"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1510
|
#: crates/pdfs-gui/src/app/main.rs:1362
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
||||||
@@ -316,38 +316,38 @@ msgstr "Drive doorzoeken"
|
|||||||
msgid "Refresh"
|
msgid "Refresh"
|
||||||
msgstr "Verversen"
|
msgstr "Verversen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1512
|
#: crates/pdfs-gui/src/app/main.rs:1364
|
||||||
msgid "Keyboard shortcuts"
|
msgid "Keyboard shortcuts"
|
||||||
msgstr "Sneltoetsen"
|
msgstr "Sneltoetsen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1516
|
#: crates/pdfs-gui/src/app/main.rs:1368
|
||||||
msgid "Files"
|
msgid "Files"
|
||||||
msgstr "Bestanden"
|
msgstr "Bestanden"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1518
|
#: crates/pdfs-gui/src/app/main.rs:1370
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
||||||
msgid "New folder"
|
msgid "New folder"
|
||||||
msgstr "Nieuwe map"
|
msgstr "Nieuwe map"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1519
|
#: crates/pdfs-gui/src/app/main.rs:1371
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
||||||
msgid "Upload files"
|
msgid "Upload files"
|
||||||
msgstr "Bestanden uploaden"
|
msgstr "Bestanden uploaden"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1520
|
#: crates/pdfs-gui/src/app/main.rs:1372
|
||||||
msgid "Back / forward"
|
msgid "Back / forward"
|
||||||
msgstr "Terug / vooruit"
|
msgstr "Terug / vooruit"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1521
|
#: crates/pdfs-gui/src/app/main.rs:1373
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
||||||
msgid "Parent folder"
|
msgid "Parent folder"
|
||||||
msgstr "Bovenliggende map"
|
msgstr "Bovenliggende map"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1522
|
#: crates/pdfs-gui/src/app/main.rs:1374
|
||||||
msgid "Grid / list"
|
msgid "Grid / list"
|
||||||
msgstr "Raster / lijst"
|
msgstr "Raster / lijst"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1523
|
#: crates/pdfs-gui/src/app/main.rs:1375
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
||||||
@@ -356,7 +356,7 @@ msgstr "Raster / lijst"
|
|||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
msgstr "Hernoemen"
|
msgstr "Hernoemen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1524 crates/pdfs-gui/src/app/main.rs:1544
|
#: crates/pdfs-gui/src/app/main.rs:1376 crates/pdfs-gui/src/app/main.rs:1396
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
||||||
@@ -366,72 +366,73 @@ msgstr "Hernoemen"
|
|||||||
msgid "Move to Trash"
|
msgid "Move to Trash"
|
||||||
msgstr "Naar prullenbak verplaatsen"
|
msgstr "Naar prullenbak verplaatsen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1525
|
#: crates/pdfs-gui/src/app/main.rs:1377
|
||||||
msgid "Clear the selection"
|
msgid "Clear the selection"
|
||||||
msgstr "Selectie wissen"
|
msgstr "Selectie wissen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1531
|
#: crates/pdfs-gui/src/app/main.rs:1383
|
||||||
msgid "Larger thumbnails"
|
msgid "Larger thumbnails"
|
||||||
msgstr "Grotere miniaturen"
|
msgstr "Grotere miniaturen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1532
|
#: crates/pdfs-gui/src/app/main.rs:1384
|
||||||
msgid "Smaller thumbnails"
|
msgid "Smaller thumbnails"
|
||||||
msgstr "Kleinere miniaturen"
|
msgstr "Kleinere miniaturen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1533
|
#: crates/pdfs-gui/src/app/main.rs:1385
|
||||||
msgid "Reset thumbnail size"
|
msgid "Reset thumbnail size"
|
||||||
msgstr "Miniatuurgrootte herstellen"
|
msgstr "Miniatuurgrootte herstellen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1534
|
#: crates/pdfs-gui/src/app/main.rs:1386
|
||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Alles selecteren"
|
msgstr "Alles selecteren"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1538
|
#: crates/pdfs-gui/src/app/main.rs:1390
|
||||||
msgid "Photo Viewer"
|
msgid "Photo Viewer"
|
||||||
msgstr "Fotoweergave"
|
msgstr "Fotoweergave"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1540
|
#: crates/pdfs-gui/src/app/main.rs:1392
|
||||||
msgid "Previous / next photo"
|
msgid "Previous / next photo"
|
||||||
msgstr "Vorige / volgende foto"
|
msgstr "Vorige / volgende foto"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1541
|
#: crates/pdfs-gui/src/app/main.rs:1393
|
||||||
msgid "First / last photo"
|
msgid "First / last photo"
|
||||||
msgstr "Eerste / laatste foto"
|
msgstr "Eerste / laatste foto"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1542
|
#: crates/pdfs-gui/src/app/main.rs:1394
|
||||||
msgid "Show details"
|
msgid "Show details"
|
||||||
msgstr "Details tonen"
|
msgstr "Details tonen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1543
|
#: crates/pdfs-gui/src/app/main.rs:1395
|
||||||
msgid "Fullscreen"
|
msgid "Fullscreen"
|
||||||
msgstr "Volledig scherm"
|
msgstr "Volledig scherm"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1545
|
#: crates/pdfs-gui/src/app/main.rs:1397
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "Sluiten"
|
msgstr "Sluiten"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1635
|
#: crates/pdfs-gui/src/app/main.rs:1487
|
||||||
msgid "Select a single item to rename it"
|
msgid "Select a single item to rename it"
|
||||||
msgstr "Selecteer één item om het te hernoemen"
|
msgstr "Selecteer één item om het te hernoemen"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1795
|
#: crates/pdfs-gui/src/app/main.rs:1647
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
||||||
msgid "Not connected"
|
msgid "Not connected"
|
||||||
msgstr "Niet verbonden"
|
msgstr "Niet verbonden"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1796
|
#: crates/pdfs-gui/src/app/main.rs:1648
|
||||||
msgid "The Proton Drive service isn't running."
|
msgid "The Proton Drive service isn't running."
|
||||||
msgstr "De Proton Drive-service draait niet."
|
msgstr "De Proton Drive-service draait niet."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1804
|
#: crates/pdfs-gui/src/app/main.rs:1656
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:336 crates/pdfs-gui/src/main.rs:444
|
#: crates/pdfs-gui/src/app/pages/status.rs:336
|
||||||
#: crates/pdfs-gui/src/prompt.rs:616 crates/pdfs-gui/src/prompt.rs:867
|
#: crates/pdfs-gui/src/hud/mod.rs:274 crates/pdfs-gui/src/hud/mod.rs:589
|
||||||
|
#: crates/pdfs-gui/src/main.rs:444
|
||||||
msgid "Connecting…"
|
msgid "Connecting…"
|
||||||
msgstr "Verbinden…"
|
msgstr "Verbinden…"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1805
|
#: crates/pdfs-gui/src/app/main.rs:1657
|
||||||
msgid "Waiting for the Proton Drive service to start."
|
msgid "Waiting for the Proton Drive service to start."
|
||||||
msgstr "Wachten tot de Proton Drive-service is gestart."
|
msgstr "Wachten tot de Proton Drive-service is gestart."
|
||||||
|
|
||||||
@@ -476,7 +477,7 @@ msgstr "Niets in het recente logboek past bij dit filter."
|
|||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/prompt.rs:595
|
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/hud/mod.rs:245
|
||||||
msgid "Retry"
|
msgid "Retry"
|
||||||
msgstr "Opnieuw proberen"
|
msgstr "Opnieuw proberen"
|
||||||
|
|
||||||
@@ -1235,6 +1236,7 @@ msgstr "De Proton Drive-service reageerde niet."
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:265
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
msgstr "Openen"
|
msgstr "Openen"
|
||||||
@@ -1425,6 +1427,7 @@ msgstr "Naam"
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:133
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Size"
|
msgid "Size"
|
||||||
msgstr "Grootte"
|
msgstr "Grootte"
|
||||||
@@ -1601,6 +1604,7 @@ msgstr[1] "{n} bestanden zijn nu alleen online"
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:134
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Modified"
|
msgid "Modified"
|
||||||
msgstr "Gewijzigd"
|
msgstr "Gewijzigd"
|
||||||
@@ -1609,6 +1613,7 @@ msgstr "Gewijzigd"
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:135
|
||||||
msgid "Location"
|
msgid "Location"
|
||||||
msgstr "Locatie"
|
msgstr "Locatie"
|
||||||
|
|
||||||
@@ -1627,6 +1632,7 @@ msgstr "Afspelen"
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:296
|
||||||
msgid "Available offline"
|
msgid "Available offline"
|
||||||
msgstr "Offline beschikbaar"
|
msgstr "Offline beschikbaar"
|
||||||
|
|
||||||
@@ -2046,7 +2052,6 @@ msgstr "Proton Drive downloaden"
|
|||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
||||||
#: crates/pdfs-gui/src/prompt.rs:568
|
|
||||||
msgid "This computer"
|
msgid "This computer"
|
||||||
msgstr "Deze computer"
|
msgstr "Deze computer"
|
||||||
|
|
||||||
@@ -2538,7 +2543,7 @@ msgid "About Sync Modes"
|
|||||||
msgstr "Over synchronisatiemodi"
|
msgstr "Over synchronisatiemodi"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
||||||
#: crates/pdfs-gui/src/prompt.rs:60
|
#: crates/pdfs-gui/src/prompt.rs:65
|
||||||
msgid "Folders"
|
msgid "Folders"
|
||||||
msgstr "Mappen"
|
msgstr "Mappen"
|
||||||
|
|
||||||
@@ -3409,7 +3414,7 @@ msgid "Select Photo to Upload"
|
|||||||
msgstr "Foto selecteren om te uploaden"
|
msgstr "Foto selecteren om te uploaden"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
||||||
#: crates/pdfs-gui/src/prompt.rs:62
|
#: crates/pdfs-gui/src/prompt.rs:67
|
||||||
msgid "Images"
|
msgid "Images"
|
||||||
msgstr "Afbeeldingen"
|
msgstr "Afbeeldingen"
|
||||||
|
|
||||||
@@ -4579,7 +4584,7 @@ msgid "Up to date"
|
|||||||
msgstr "Bijgewerkt"
|
msgstr "Bijgewerkt"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
||||||
#: crates/pdfs-gui/src/prompt.rs:880 crates/pdfs-gui/src/prompt.rs:893
|
#: crates/pdfs-gui/src/hud/mod.rs:602 crates/pdfs-gui/src/hud/mod.rs:616
|
||||||
msgid "Proton Drive isn't running"
|
msgid "Proton Drive isn't running"
|
||||||
msgstr "Proton Drive draait niet"
|
msgstr "Proton Drive draait niet"
|
||||||
|
|
||||||
@@ -5120,6 +5125,7 @@ msgid "Proton Drive"
|
|||||||
msgstr "Proton Drive"
|
msgstr "Proton Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:263
|
||||||
msgid "Open folder"
|
msgid "Open folder"
|
||||||
msgstr "Map openen"
|
msgstr "Map openen"
|
||||||
|
|
||||||
@@ -5501,6 +5507,137 @@ msgstr "(geen vastgezette bestanden — typ om te zoeken)"
|
|||||||
msgid "(cannot resolve app dirs)"
|
msgid "(cannot resolve app dirs)"
|
||||||
msgstr "(kan app-mappen niet bepalen)"
|
msgstr "(kan app-mappen niet bepalen)"
|
||||||
|
|
||||||
|
#. Translators: the launcher's section of recently opened files.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:57
|
||||||
|
msgid "Recent"
|
||||||
|
msgstr "Recent"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:58
|
||||||
|
msgid "Pinned in Proton Drive"
|
||||||
|
msgstr "Vastgezet in Proton Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:59
|
||||||
|
msgid "Best matches"
|
||||||
|
msgstr "Beste overeenkomsten"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:159
|
||||||
|
msgid "Search Proton Drive"
|
||||||
|
msgstr "Proton Drive doorzoeken"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:180
|
||||||
|
msgid "Search in Drive and on this computer"
|
||||||
|
msgstr "Zoeken in Drive en op deze computer"
|
||||||
|
|
||||||
|
#. Translators: the Escape key, shown as a hint that it closes the window.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:191
|
||||||
|
msgid "Esc"
|
||||||
|
msgstr "Esc"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:232 crates/pdfs-gui/src/hud/mod.rs:862
|
||||||
|
msgid "No results"
|
||||||
|
msgstr "Geen resultaten"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:240
|
||||||
|
msgid "Proton Drive is not running"
|
||||||
|
msgstr "Proton Drive draait niet"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:241
|
||||||
|
msgid "Start Proton Drive to search your files."
|
||||||
|
msgstr "Start Proton Drive om je bestanden te doorzoeken."
|
||||||
|
|
||||||
|
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:282
|
||||||
|
msgid "↑↓ navigate · ↵ open · Tab filter"
|
||||||
|
msgstr "↑↓ navigeren · ↵ openen · Tab filter"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:864
|
||||||
|
msgid "Search your Drive"
|
||||||
|
msgstr "Je Drive doorzoeken"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:869
|
||||||
|
msgid "Still indexing this computer — local results will fill in shortly."
|
||||||
|
msgstr ""
|
||||||
|
"Deze computer wordt nog geïndexeerd — lokale resultaten verschijnen zo."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:871
|
||||||
|
msgid "Try a different search, or another filter."
|
||||||
|
msgstr "Probeer een andere zoekopdracht of een ander filter."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:873
|
||||||
|
msgid "Start typing to search Proton Drive and the files on this computer."
|
||||||
|
msgstr ""
|
||||||
|
"Begin met typen om Proton Drive en de bestanden op deze computer te "
|
||||||
|
"doorzoeken."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:900
|
||||||
|
msgid "No pinned files"
|
||||||
|
msgstr "Geen vastgezette bestanden"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:901
|
||||||
|
#, rust-format
|
||||||
|
msgid "{n} pinned file"
|
||||||
|
msgid_plural "{n} pinned files"
|
||||||
|
msgstr[0] "{n} vastgezet bestand"
|
||||||
|
msgstr[1] "{n} vastgezette bestanden"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:914
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
||||||
|
msgstr "{drive} in Drive · {local} op deze computer · indexeren…"
|
||||||
|
|
||||||
|
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:919
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer"
|
||||||
|
msgstr "{drive} in Drive · {local} op deze computer"
|
||||||
|
|
||||||
|
#. Translators: {name} is a file name.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1050
|
||||||
|
#, rust-format
|
||||||
|
msgid "Opening {name}…"
|
||||||
|
msgstr "{name} openen…"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1090
|
||||||
|
#, rust-format
|
||||||
|
msgid "Could not open: {message}"
|
||||||
|
msgstr "Kon niet openen: {message}"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1094
|
||||||
|
msgid "Couldn't reach Proton Drive"
|
||||||
|
msgstr "Kon Proton Drive niet bereiken"
|
||||||
|
|
||||||
|
#. Translators: a heading in the launcher's preview pane, followed by
|
||||||
|
#. "Available offline", "Downloaded", "Online only" or "On this computer".
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:138
|
||||||
|
msgid "Availability"
|
||||||
|
msgstr "Beschikbaarheid"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:157
|
||||||
|
msgid "Show in Folder"
|
||||||
|
msgstr "In map tonen"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:160
|
||||||
|
msgid "Copy Path"
|
||||||
|
msgstr "Pad kopiëren"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:166
|
||||||
|
msgid "Path copied"
|
||||||
|
msgstr "Pad gekopieerd"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:291
|
||||||
|
msgid "On this computer"
|
||||||
|
msgstr "Op deze computer"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:303
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Downloaded"
|
||||||
|
msgstr "Gedownload"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:308
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Online only"
|
||||||
|
msgstr "Alleen online"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/i18n.rs:84
|
#: crates/pdfs-gui/src/i18n.rs:84
|
||||||
msgid "Kosovo"
|
msgid "Kosovo"
|
||||||
msgstr "Kosovo"
|
msgstr "Kosovo"
|
||||||
@@ -5654,16 +5791,16 @@ msgid "Quit"
|
|||||||
msgstr "Afsluiten"
|
msgstr "Afsluiten"
|
||||||
|
|
||||||
#. Translators: a filter chip that shows every kind of result.
|
#. Translators: a filter chip that shows every kind of result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:59
|
#: crates/pdfs-gui/src/prompt.rs:64
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Alles"
|
msgstr "Alles"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:61
|
#: crates/pdfs-gui/src/prompt.rs:66
|
||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Documenten"
|
msgstr "Documenten"
|
||||||
|
|
||||||
#. Translators: a filter chip for audio and video files.
|
#. Translators: a filter chip for audio and video files.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:64
|
#: crates/pdfs-gui/src/prompt.rs:69
|
||||||
msgid "Media"
|
msgid "Media"
|
||||||
msgstr "Media"
|
msgstr "Media"
|
||||||
|
|
||||||
@@ -5672,143 +5809,49 @@ msgstr "Media"
|
|||||||
msgid "My files / {path}"
|
msgid "My files / {path}"
|
||||||
msgstr "Mijn bestanden / {path}"
|
msgstr "Mijn bestanden / {path}"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:520
|
|
||||||
msgid "Search Proton Drive"
|
|
||||||
msgstr "Proton Drive doorzoeken"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:541
|
|
||||||
msgid "Search in Drive and on this computer"
|
|
||||||
msgstr "Zoeken in Drive en op deze computer"
|
|
||||||
|
|
||||||
#. Translators: the Escape key, shown as a hint that it closes the window.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:552
|
|
||||||
msgid "Esc"
|
|
||||||
msgstr "Esc"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:566 crates/pdfs-gui/src/prompt.rs:1085
|
|
||||||
msgid "Best matches"
|
|
||||||
msgstr "Beste overeenkomsten"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:582 crates/pdfs-gui/src/prompt.rs:1109
|
|
||||||
msgid "No results"
|
|
||||||
msgstr "Geen resultaten"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:590
|
|
||||||
msgid "Proton Drive is not running"
|
|
||||||
msgstr "Proton Drive draait niet"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:591
|
|
||||||
msgid "Start Proton Drive to search your files."
|
|
||||||
msgstr "Start Proton Drive om je bestanden te doorzoeken."
|
|
||||||
|
|
||||||
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:624
|
|
||||||
msgid "↑↓ navigate · ↵ open · Tab filter"
|
|
||||||
msgstr "↑↓ navigeren · ↵ openen · Tab filter"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1096
|
|
||||||
msgid "Pinned in Proton Drive"
|
|
||||||
msgstr "Vastgezet in Proton Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1111
|
|
||||||
msgid "Search your Drive"
|
|
||||||
msgstr "Je Drive doorzoeken"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1116
|
|
||||||
msgid "Still indexing this computer — local results will fill in shortly."
|
|
||||||
msgstr ""
|
|
||||||
"Deze computer wordt nog geïndexeerd — lokale resultaten verschijnen zo."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1118
|
|
||||||
msgid "Try a different search, or another filter."
|
|
||||||
msgstr "Probeer een andere zoekopdracht of een ander filter."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1120
|
|
||||||
msgid "Start typing to search Proton Drive and the files on this computer."
|
|
||||||
msgstr ""
|
|
||||||
"Begin met typen om Proton Drive en de bestanden op deze computer te "
|
|
||||||
"doorzoeken."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1146
|
|
||||||
msgid "No pinned files"
|
|
||||||
msgstr "Geen vastgezette bestanden"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1147
|
|
||||||
#, rust-format
|
|
||||||
msgid "{n} pinned file"
|
|
||||||
msgid_plural "{n} pinned files"
|
|
||||||
msgstr[0] "{n} vastgezet bestand"
|
|
||||||
msgstr[1] "{n} vastgezette bestanden"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1156
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
|
||||||
msgstr "{drive} in Drive · {local} op deze computer · indexeren…"
|
|
||||||
|
|
||||||
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1161
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer"
|
|
||||||
msgstr "{drive} in Drive · {local} op deze computer"
|
|
||||||
|
|
||||||
#. Translators: {name} is a file name.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1294
|
|
||||||
#, rust-format
|
|
||||||
msgid "Opening {name}…"
|
|
||||||
msgstr "{name} openen…"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1317
|
|
||||||
#, rust-format
|
|
||||||
msgid "Could not open: {message}"
|
|
||||||
msgstr "Kon niet openen: {message}"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1321
|
|
||||||
msgid "Couldn't reach Proton Drive"
|
|
||||||
msgstr "Kon Proton Drive niet bereiken"
|
|
||||||
|
|
||||||
#. Translators: the user's home folder, as the location of a result.
|
#. Translators: the user's home folder, as the location of a result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1430
|
#: crates/pdfs-gui/src/prompt.rs:455
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Persoonlijke map"
|
msgstr "Persoonlijke map"
|
||||||
|
|
||||||
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1432
|
#: crates/pdfs-gui/src/prompt.rs:457
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "Home / {path}"
|
msgid "Home / {path}"
|
||||||
msgstr "Persoonlijke map / {path}"
|
msgstr "Persoonlijke map / {path}"
|
||||||
|
|
||||||
#. Translators: the age of a file modified under a minute ago.
|
#. Translators: the age of a file modified under a minute ago.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1452
|
#: crates/pdfs-gui/src/prompt.rs:477
|
||||||
msgctxt "age"
|
msgctxt "age"
|
||||||
msgid "now"
|
msgid "now"
|
||||||
msgstr "nu"
|
msgstr "nu"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of minutes.
|
#. Translators: a compact file age; {count} is a number of minutes.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1454
|
#: crates/pdfs-gui/src/prompt.rs:479
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}m"
|
msgid "{count}m"
|
||||||
msgstr "{count}m"
|
msgstr "{count}m"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of hours.
|
#. Translators: a compact file age; {count} is a number of hours.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1456
|
#: crates/pdfs-gui/src/prompt.rs:481
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}h"
|
msgid "{count}h"
|
||||||
msgstr "{count}u"
|
msgstr "{count}u"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of days.
|
#. Translators: a compact file age; {count} is a number of days.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1458
|
#: crates/pdfs-gui/src/prompt.rs:483
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}d"
|
msgid "{count}d"
|
||||||
msgstr "{count}d"
|
msgstr "{count}d"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of months.
|
#. Translators: a compact file age; {count} is a number of months.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1460
|
#: crates/pdfs-gui/src/prompt.rs:485
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}mo"
|
msgid "{count}mo"
|
||||||
msgstr "{count}mnd"
|
msgstr "{count}mnd"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of years.
|
#. Translators: a compact file age; {count} is a number of years.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1462
|
#: crates/pdfs-gui/src/prompt.rs:487
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}y"
|
msgid "{count}y"
|
||||||
msgstr "{count}j"
|
msgstr "{count}j"
|
||||||
@@ -5957,9 +6000,6 @@ msgstr "kan de achtergronddienst van Proton Drive niet bereiken: {error}"
|
|||||||
#~ msgid "Use this computer's identity"
|
#~ msgid "Use this computer's identity"
|
||||||
#~ msgstr "Identiteit van deze computer gebruiken"
|
#~ msgstr "Identiteit van deze computer gebruiken"
|
||||||
|
|
||||||
#~ msgid "On this computer"
|
|
||||||
#~ msgstr "Op deze computer"
|
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
#~ "Where Proton Drive lives on this machine. Synced folders keep a full copy "
|
#~ "Where Proton Drive lives on this machine. Synced folders keep a full copy "
|
||||||
#~ "on this disk; on-demand folders keep the files in Proton Drive only and "
|
#~ "on this disk; on-demand folders keep the files in Proton Drive only and "
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: proton-drive-linux 2.5.0\n"
|
"Project-Id-Version: proton-drive-linux 2.5.0\n"
|
||||||
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
||||||
"POT-Creation-Date: 2026-09-28 16:17+0200\n"
|
"POT-Creation-Date: 2026-09-28 22:51+0200\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -60,7 +60,7 @@ msgstr ""
|
|||||||
msgid "%A, %B %-d, %Y, %H:%M"
|
msgid "%A, %B %-d, %Y, %H:%M"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:882
|
#: crates/pdfs-gui/src/app/main.rs:734
|
||||||
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
||||||
@@ -71,91 +71,91 @@ msgstr ""
|
|||||||
msgid "My files"
|
msgid "My files"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:885 crates/pdfs-gui/src/app/main.rs:1529
|
#: crates/pdfs-gui/src/app/main.rs:737 crates/pdfs-gui/src/app/main.rs:1381
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
||||||
msgid "Photos"
|
msgid "Photos"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:890
|
#: crates/pdfs-gui/src/app/main.rs:742
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
||||||
msgid "Shared with me"
|
msgid "Shared with me"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:895
|
#: crates/pdfs-gui/src/app/main.rs:747
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
||||||
msgid "Shared by me"
|
msgid "Shared by me"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:898
|
#: crates/pdfs-gui/src/app/main.rs:750
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
||||||
msgid "Computers"
|
msgid "Computers"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:899
|
#: crates/pdfs-gui/src/app/main.rs:751
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
||||||
msgid "Trash"
|
msgid "Trash"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:901
|
#: crates/pdfs-gui/src/app/main.rs:753
|
||||||
msgid "Sync"
|
msgid "Sync"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Translators: a failed action's toast. {what} says what failed, such as
|
#. Translators: a failed action's toast. {what} says what failed, such as
|
||||||
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
||||||
#. from the mount service.
|
#. from the mount service.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1218
|
#: crates/pdfs-gui/src/app/main.rs:1070
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{what}: {detail}"
|
msgid "{what}: {detail}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1237
|
#: crates/pdfs-gui/src/app/main.rs:1089
|
||||||
msgid "You're offline"
|
msgid "You're offline"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1238
|
#: crates/pdfs-gui/src/app/main.rs:1090
|
||||||
msgid "That's not there any more"
|
msgid "That's not there any more"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1239
|
#: crates/pdfs-gui/src/app/main.rs:1091
|
||||||
msgid "You don't have access to that"
|
msgid "You don't have access to that"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1240
|
#: crates/pdfs-gui/src/app/main.rs:1092
|
||||||
msgid "Something changed this first"
|
msgid "Something changed this first"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1241
|
#: crates/pdfs-gui/src/app/main.rs:1093
|
||||||
msgid "Your Proton Drive is full"
|
msgid "Your Proton Drive is full"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1282 crates/pdfs-gui/src/app/main.rs:1511
|
#: crates/pdfs-gui/src/app/main.rs:1134 crates/pdfs-gui/src/app/main.rs:1363
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
||||||
msgid "Preferences"
|
msgid "Preferences"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1283
|
#: crates/pdfs-gui/src/app/main.rs:1135
|
||||||
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
||||||
msgid "Keyboard Shortcuts"
|
msgid "Keyboard Shortcuts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1285
|
#: crates/pdfs-gui/src/app/main.rs:1137
|
||||||
msgid "About Proton Drive for Linux"
|
msgid "About Proton Drive for Linux"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1290
|
#: crates/pdfs-gui/src/app/main.rs:1142
|
||||||
msgid "Main menu"
|
msgid "Main menu"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1346
|
#: crates/pdfs-gui/src/app/main.rs:1198
|
||||||
msgid "Proton Drive for Linux"
|
msgid "Proton Drive for Linux"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1354
|
#: crates/pdfs-gui/src/app/main.rs:1206
|
||||||
msgid ""
|
msgid ""
|
||||||
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -163,23 +163,23 @@ msgid ""
|
|||||||
"AG."
|
"AG."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1359
|
#: crates/pdfs-gui/src/app/main.rs:1211
|
||||||
msgid "Source Code"
|
msgid "Source Code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1363
|
#: crates/pdfs-gui/src/app/main.rs:1215
|
||||||
msgid "Proton Terms of Service"
|
msgid "Proton Terms of Service"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1367
|
#: crates/pdfs-gui/src/app/main.rs:1219
|
||||||
msgid "Proton Privacy Policy"
|
msgid "Proton Privacy Policy"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1372
|
#: crates/pdfs-gui/src/app/main.rs:1224
|
||||||
msgid "Disclaimer"
|
msgid "Disclaimer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1376
|
#: crates/pdfs-gui/src/app/main.rs:1228
|
||||||
msgid ""
|
msgid ""
|
||||||
"This is an unofficial, community-built client. It is not affiliated with, "
|
"This is an unofficial, community-built client. It is not affiliated with, "
|
||||||
"endorsed by, or supported by Proton AG.\n"
|
"endorsed by, or supported by Proton AG.\n"
|
||||||
@@ -192,21 +192,21 @@ msgid ""
|
|||||||
"of any data you cannot afford to lose."
|
"of any data you cannot afford to lose."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1384
|
#: crates/pdfs-gui/src/app/main.rs:1236
|
||||||
msgid ""
|
msgid ""
|
||||||
"Places are named with data from <a href=\"https://www.geonames.org/"
|
"Places are named with data from <a href=\"https://www.geonames.org/"
|
||||||
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
||||||
"licenses/by/4.0/\">CC BY 4.0</a>."
|
"licenses/by/4.0/\">CC BY 4.0</a>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1392
|
#: crates/pdfs-gui/src/app/main.rs:1244
|
||||||
msgid ""
|
msgid ""
|
||||||
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
||||||
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
||||||
"public domain."
|
"public domain."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1400
|
#: crates/pdfs-gui/src/app/main.rs:1252
|
||||||
msgid ""
|
msgid ""
|
||||||
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
||||||
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
||||||
@@ -217,21 +217,21 @@ msgstr ""
|
|||||||
|
|
||||||
#. Translators: replace with your name(s) and email address(es), one
|
#. Translators: replace with your name(s) and email address(es), one
|
||||||
#. per line, to be credited in the About dialog.
|
#. per line, to be credited in the About dialog.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1411
|
#: crates/pdfs-gui/src/app/main.rs:1263
|
||||||
msgid "translator-credits"
|
msgid "translator-credits"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1449
|
#: crates/pdfs-gui/src/app/main.rs:1301
|
||||||
msgid "Stop Proton Drive?"
|
msgid "Stop Proton Drive?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1450
|
#: crates/pdfs-gui/src/app/main.rs:1302
|
||||||
msgid ""
|
msgid ""
|
||||||
"My files goes offline and nothing syncs until you connect again. Proton "
|
"My files goes offline and nothing syncs until you connect again. Proton "
|
||||||
"Drive starts again at your next login."
|
"Drive starts again at your next login."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1452 crates/pdfs-gui/src/app/main.rs:1727
|
#: crates/pdfs-gui/src/app/main.rs:1304 crates/pdfs-gui/src/app/main.rs:1579
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
||||||
@@ -260,30 +260,30 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1453
|
#: crates/pdfs-gui/src/app/main.rs:1305
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Stop"
|
msgid "Stop"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1470
|
#: crates/pdfs-gui/src/app/main.rs:1322
|
||||||
msgid "Couldn't stop Proton Drive"
|
msgid "Couldn't stop Proton Drive"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1471
|
#: crates/pdfs-gui/src/app/main.rs:1323
|
||||||
msgid "The Proton Drive service did not stop."
|
msgid "The Proton Drive service did not stop."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1507
|
#: crates/pdfs-gui/src/app/main.rs:1359
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
||||||
msgid "General"
|
msgid "General"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1509
|
#: crates/pdfs-gui/src/app/main.rs:1361
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
||||||
msgid "Search Drive"
|
msgid "Search Drive"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1510
|
#: crates/pdfs-gui/src/app/main.rs:1362
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
||||||
@@ -292,38 +292,38 @@ msgstr ""
|
|||||||
msgid "Refresh"
|
msgid "Refresh"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1512
|
#: crates/pdfs-gui/src/app/main.rs:1364
|
||||||
msgid "Keyboard shortcuts"
|
msgid "Keyboard shortcuts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1516
|
#: crates/pdfs-gui/src/app/main.rs:1368
|
||||||
msgid "Files"
|
msgid "Files"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1518
|
#: crates/pdfs-gui/src/app/main.rs:1370
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
||||||
msgid "New folder"
|
msgid "New folder"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1519
|
#: crates/pdfs-gui/src/app/main.rs:1371
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
||||||
msgid "Upload files"
|
msgid "Upload files"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1520
|
#: crates/pdfs-gui/src/app/main.rs:1372
|
||||||
msgid "Back / forward"
|
msgid "Back / forward"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1521
|
#: crates/pdfs-gui/src/app/main.rs:1373
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
||||||
msgid "Parent folder"
|
msgid "Parent folder"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1522
|
#: crates/pdfs-gui/src/app/main.rs:1374
|
||||||
msgid "Grid / list"
|
msgid "Grid / list"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1523
|
#: crates/pdfs-gui/src/app/main.rs:1375
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
||||||
@@ -332,7 +332,7 @@ msgstr ""
|
|||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1524 crates/pdfs-gui/src/app/main.rs:1544
|
#: crates/pdfs-gui/src/app/main.rs:1376 crates/pdfs-gui/src/app/main.rs:1396
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
||||||
@@ -342,72 +342,73 @@ msgstr ""
|
|||||||
msgid "Move to Trash"
|
msgid "Move to Trash"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1525
|
#: crates/pdfs-gui/src/app/main.rs:1377
|
||||||
msgid "Clear the selection"
|
msgid "Clear the selection"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1531
|
#: crates/pdfs-gui/src/app/main.rs:1383
|
||||||
msgid "Larger thumbnails"
|
msgid "Larger thumbnails"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1532
|
#: crates/pdfs-gui/src/app/main.rs:1384
|
||||||
msgid "Smaller thumbnails"
|
msgid "Smaller thumbnails"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1533
|
#: crates/pdfs-gui/src/app/main.rs:1385
|
||||||
msgid "Reset thumbnail size"
|
msgid "Reset thumbnail size"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1534
|
#: crates/pdfs-gui/src/app/main.rs:1386
|
||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1538
|
#: crates/pdfs-gui/src/app/main.rs:1390
|
||||||
msgid "Photo Viewer"
|
msgid "Photo Viewer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1540
|
#: crates/pdfs-gui/src/app/main.rs:1392
|
||||||
msgid "Previous / next photo"
|
msgid "Previous / next photo"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1541
|
#: crates/pdfs-gui/src/app/main.rs:1393
|
||||||
msgid "First / last photo"
|
msgid "First / last photo"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1542
|
#: crates/pdfs-gui/src/app/main.rs:1394
|
||||||
msgid "Show details"
|
msgid "Show details"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1543
|
#: crates/pdfs-gui/src/app/main.rs:1395
|
||||||
msgid "Fullscreen"
|
msgid "Fullscreen"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1545
|
#: crates/pdfs-gui/src/app/main.rs:1397
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1635
|
#: crates/pdfs-gui/src/app/main.rs:1487
|
||||||
msgid "Select a single item to rename it"
|
msgid "Select a single item to rename it"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1795
|
#: crates/pdfs-gui/src/app/main.rs:1647
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
||||||
msgid "Not connected"
|
msgid "Not connected"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1796
|
#: crates/pdfs-gui/src/app/main.rs:1648
|
||||||
msgid "The Proton Drive service isn't running."
|
msgid "The Proton Drive service isn't running."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1804
|
#: crates/pdfs-gui/src/app/main.rs:1656
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:336 crates/pdfs-gui/src/main.rs:444
|
#: crates/pdfs-gui/src/app/pages/status.rs:336
|
||||||
#: crates/pdfs-gui/src/prompt.rs:616 crates/pdfs-gui/src/prompt.rs:867
|
#: crates/pdfs-gui/src/hud/mod.rs:274 crates/pdfs-gui/src/hud/mod.rs:589
|
||||||
|
#: crates/pdfs-gui/src/main.rs:444
|
||||||
msgid "Connecting…"
|
msgid "Connecting…"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1805
|
#: crates/pdfs-gui/src/app/main.rs:1657
|
||||||
msgid "Waiting for the Proton Drive service to start."
|
msgid "Waiting for the Proton Drive service to start."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -452,7 +453,7 @@ msgstr ""
|
|||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/prompt.rs:595
|
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/hud/mod.rs:245
|
||||||
msgid "Retry"
|
msgid "Retry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1203,6 +1204,7 @@ msgstr ""
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:265
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1393,6 +1395,7 @@ msgstr ""
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:133
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Size"
|
msgid "Size"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1567,6 +1570,7 @@ msgstr[1] ""
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:134
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Modified"
|
msgid "Modified"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -1575,6 +1579,7 @@ msgstr ""
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:135
|
||||||
msgid "Location"
|
msgid "Location"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -1593,6 +1598,7 @@ msgstr ""
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:296
|
||||||
msgid "Available offline"
|
msgid "Available offline"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2008,7 +2014,6 @@ msgstr ""
|
|||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
||||||
#: crates/pdfs-gui/src/prompt.rs:568
|
|
||||||
msgid "This computer"
|
msgid "This computer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -2453,7 +2458,7 @@ msgid "About Sync Modes"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
||||||
#: crates/pdfs-gui/src/prompt.rs:60
|
#: crates/pdfs-gui/src/prompt.rs:65
|
||||||
msgid "Folders"
|
msgid "Folders"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -3289,7 +3294,7 @@ msgid "Select Photo to Upload"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
||||||
#: crates/pdfs-gui/src/prompt.rs:62
|
#: crates/pdfs-gui/src/prompt.rs:67
|
||||||
msgid "Images"
|
msgid "Images"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4414,7 +4419,7 @@ msgid "Up to date"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
||||||
#: crates/pdfs-gui/src/prompt.rs:880 crates/pdfs-gui/src/prompt.rs:893
|
#: crates/pdfs-gui/src/hud/mod.rs:602 crates/pdfs-gui/src/hud/mod.rs:616
|
||||||
msgid "Proton Drive isn't running"
|
msgid "Proton Drive isn't running"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -4930,6 +4935,7 @@ msgid "Proton Drive"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:263
|
||||||
msgid "Open folder"
|
msgid "Open folder"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5304,6 +5310,134 @@ msgstr ""
|
|||||||
msgid "(cannot resolve app dirs)"
|
msgid "(cannot resolve app dirs)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. Translators: the launcher's section of recently opened files.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:57
|
||||||
|
msgid "Recent"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:58
|
||||||
|
msgid "Pinned in Proton Drive"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:59
|
||||||
|
msgid "Best matches"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:159
|
||||||
|
msgid "Search Proton Drive"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:180
|
||||||
|
msgid "Search in Drive and on this computer"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. Translators: the Escape key, shown as a hint that it closes the window.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:191
|
||||||
|
msgid "Esc"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:232 crates/pdfs-gui/src/hud/mod.rs:862
|
||||||
|
msgid "No results"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:240
|
||||||
|
msgid "Proton Drive is not running"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:241
|
||||||
|
msgid "Start Proton Drive to search your files."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:282
|
||||||
|
msgid "↑↓ navigate · ↵ open · Tab filter"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:864
|
||||||
|
msgid "Search your Drive"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:869
|
||||||
|
msgid "Still indexing this computer — local results will fill in shortly."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:871
|
||||||
|
msgid "Try a different search, or another filter."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:873
|
||||||
|
msgid "Start typing to search Proton Drive and the files on this computer."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:900
|
||||||
|
msgid "No pinned files"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:901
|
||||||
|
#, rust-format
|
||||||
|
msgid "{n} pinned file"
|
||||||
|
msgid_plural "{n} pinned files"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:914
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:919
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. Translators: {name} is a file name.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1050
|
||||||
|
#, rust-format
|
||||||
|
msgid "Opening {name}…"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1090
|
||||||
|
#, rust-format
|
||||||
|
msgid "Could not open: {message}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1094
|
||||||
|
msgid "Couldn't reach Proton Drive"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. Translators: a heading in the launcher's preview pane, followed by
|
||||||
|
#. "Available offline", "Downloaded", "Online only" or "On this computer".
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:138
|
||||||
|
msgid "Availability"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:157
|
||||||
|
msgid "Show in Folder"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:160
|
||||||
|
msgid "Copy Path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:166
|
||||||
|
msgid "Path copied"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:291
|
||||||
|
msgid "On this computer"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:303
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Downloaded"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:308
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Online only"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/i18n.rs:84
|
#: crates/pdfs-gui/src/i18n.rs:84
|
||||||
msgid "Kosovo"
|
msgid "Kosovo"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -5457,16 +5591,16 @@ msgid "Quit"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Translators: a filter chip that shows every kind of result.
|
#. Translators: a filter chip that shows every kind of result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:59
|
#: crates/pdfs-gui/src/prompt.rs:64
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:61
|
#: crates/pdfs-gui/src/prompt.rs:66
|
||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Translators: a filter chip for audio and video files.
|
#. Translators: a filter chip for audio and video files.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:64
|
#: crates/pdfs-gui/src/prompt.rs:69
|
||||||
msgid "Media"
|
msgid "Media"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -5475,140 +5609,49 @@ msgstr ""
|
|||||||
msgid "My files / {path}"
|
msgid "My files / {path}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:520
|
|
||||||
msgid "Search Proton Drive"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:541
|
|
||||||
msgid "Search in Drive and on this computer"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. Translators: the Escape key, shown as a hint that it closes the window.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:552
|
|
||||||
msgid "Esc"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:566 crates/pdfs-gui/src/prompt.rs:1085
|
|
||||||
msgid "Best matches"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:582 crates/pdfs-gui/src/prompt.rs:1109
|
|
||||||
msgid "No results"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:590
|
|
||||||
msgid "Proton Drive is not running"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:591
|
|
||||||
msgid "Start Proton Drive to search your files."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:624
|
|
||||||
msgid "↑↓ navigate · ↵ open · Tab filter"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1096
|
|
||||||
msgid "Pinned in Proton Drive"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1111
|
|
||||||
msgid "Search your Drive"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1116
|
|
||||||
msgid "Still indexing this computer — local results will fill in shortly."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1118
|
|
||||||
msgid "Try a different search, or another filter."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1120
|
|
||||||
msgid "Start typing to search Proton Drive and the files on this computer."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1146
|
|
||||||
msgid "No pinned files"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1147
|
|
||||||
#, rust-format
|
|
||||||
msgid "{n} pinned file"
|
|
||||||
msgid_plural "{n} pinned files"
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1156
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1161
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. Translators: {name} is a file name.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1294
|
|
||||||
#, rust-format
|
|
||||||
msgid "Opening {name}…"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1317
|
|
||||||
#, rust-format
|
|
||||||
msgid "Could not open: {message}"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1321
|
|
||||||
msgid "Couldn't reach Proton Drive"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. Translators: the user's home folder, as the location of a result.
|
#. Translators: the user's home folder, as the location of a result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1430
|
#: crates/pdfs-gui/src/prompt.rs:455
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1432
|
#: crates/pdfs-gui/src/prompt.rs:457
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "Home / {path}"
|
msgid "Home / {path}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Translators: the age of a file modified under a minute ago.
|
#. Translators: the age of a file modified under a minute ago.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1452
|
#: crates/pdfs-gui/src/prompt.rs:477
|
||||||
msgctxt "age"
|
msgctxt "age"
|
||||||
msgid "now"
|
msgid "now"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of minutes.
|
#. Translators: a compact file age; {count} is a number of minutes.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1454
|
#: crates/pdfs-gui/src/prompt.rs:479
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}m"
|
msgid "{count}m"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of hours.
|
#. Translators: a compact file age; {count} is a number of hours.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1456
|
#: crates/pdfs-gui/src/prompt.rs:481
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}h"
|
msgid "{count}h"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of days.
|
#. Translators: a compact file age; {count} is a number of days.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1458
|
#: crates/pdfs-gui/src/prompt.rs:483
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}d"
|
msgid "{count}d"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of months.
|
#. Translators: a compact file age; {count} is a number of months.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1460
|
#: crates/pdfs-gui/src/prompt.rs:485
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}mo"
|
msgid "{count}mo"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of years.
|
#. Translators: a compact file age; {count} is a number of years.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1462
|
#: crates/pdfs-gui/src/prompt.rs:487
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}y"
|
msgid "{count}y"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: proton-drive-linux 2.1.0\n"
|
"Project-Id-Version: proton-drive-linux 2.1.0\n"
|
||||||
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
||||||
"POT-Creation-Date: 2026-09-28 16:17+0200\n"
|
"POT-Creation-Date: 2026-09-28 22:51+0200\n"
|
||||||
"PO-Revision-Date: 2026-09-23 12:00+0200\n"
|
"PO-Revision-Date: 2026-09-23 12:00+0200\n"
|
||||||
"Last-Translator: Proton Drive for Linux contributors\n"
|
"Last-Translator: Proton Drive for Linux contributors\n"
|
||||||
"Language-Team: Polish\n"
|
"Language-Team: Polish\n"
|
||||||
@@ -59,7 +59,7 @@ msgstr "%-d %b %Y"
|
|||||||
msgid "%A, %B %-d, %Y, %H:%M"
|
msgid "%A, %B %-d, %Y, %H:%M"
|
||||||
msgstr "%A, %-d %B %Y, %H:%M"
|
msgstr "%A, %-d %B %Y, %H:%M"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:882
|
#: crates/pdfs-gui/src/app/main.rs:734
|
||||||
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
||||||
@@ -70,91 +70,91 @@ msgstr "%A, %-d %B %Y, %H:%M"
|
|||||||
msgid "My files"
|
msgid "My files"
|
||||||
msgstr "Moje pliki"
|
msgstr "Moje pliki"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:885 crates/pdfs-gui/src/app/main.rs:1529
|
#: crates/pdfs-gui/src/app/main.rs:737 crates/pdfs-gui/src/app/main.rs:1381
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
||||||
msgid "Photos"
|
msgid "Photos"
|
||||||
msgstr "Zdjęcia"
|
msgstr "Zdjęcia"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:890
|
#: crates/pdfs-gui/src/app/main.rs:742
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
||||||
msgid "Shared with me"
|
msgid "Shared with me"
|
||||||
msgstr "Udostępnione mi"
|
msgstr "Udostępnione mi"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:895
|
#: crates/pdfs-gui/src/app/main.rs:747
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
||||||
msgid "Shared by me"
|
msgid "Shared by me"
|
||||||
msgstr "Udostępnione przeze mnie"
|
msgstr "Udostępnione przeze mnie"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:898
|
#: crates/pdfs-gui/src/app/main.rs:750
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
||||||
msgid "Computers"
|
msgid "Computers"
|
||||||
msgstr "Komputery"
|
msgstr "Komputery"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:899
|
#: crates/pdfs-gui/src/app/main.rs:751
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
||||||
msgid "Trash"
|
msgid "Trash"
|
||||||
msgstr "Kosz"
|
msgstr "Kosz"
|
||||||
|
|
||||||
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:901
|
#: crates/pdfs-gui/src/app/main.rs:753
|
||||||
msgid "Sync"
|
msgid "Sync"
|
||||||
msgstr "Synchronizacja"
|
msgstr "Synchronizacja"
|
||||||
|
|
||||||
#. Translators: a failed action's toast. {what} says what failed, such as
|
#. Translators: a failed action's toast. {what} says what failed, such as
|
||||||
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
||||||
#. from the mount service.
|
#. from the mount service.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1218
|
#: crates/pdfs-gui/src/app/main.rs:1070
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{what}: {detail}"
|
msgid "{what}: {detail}"
|
||||||
msgstr "{what}: {detail}"
|
msgstr "{what}: {detail}"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1237
|
#: crates/pdfs-gui/src/app/main.rs:1089
|
||||||
msgid "You're offline"
|
msgid "You're offline"
|
||||||
msgstr "Brak połączenia z siecią"
|
msgstr "Brak połączenia z siecią"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1238
|
#: crates/pdfs-gui/src/app/main.rs:1090
|
||||||
msgid "That's not there any more"
|
msgid "That's not there any more"
|
||||||
msgstr "Ten element już nie istnieje"
|
msgstr "Ten element już nie istnieje"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1239
|
#: crates/pdfs-gui/src/app/main.rs:1091
|
||||||
msgid "You don't have access to that"
|
msgid "You don't have access to that"
|
||||||
msgstr "Brak dostępu do tego elementu"
|
msgstr "Brak dostępu do tego elementu"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1240
|
#: crates/pdfs-gui/src/app/main.rs:1092
|
||||||
msgid "Something changed this first"
|
msgid "Something changed this first"
|
||||||
msgstr "Coś zmieniło ten element wcześniej"
|
msgstr "Coś zmieniło ten element wcześniej"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1241
|
#: crates/pdfs-gui/src/app/main.rs:1093
|
||||||
msgid "Your Proton Drive is full"
|
msgid "Your Proton Drive is full"
|
||||||
msgstr "Proton Drive jest pełny"
|
msgstr "Proton Drive jest pełny"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1282 crates/pdfs-gui/src/app/main.rs:1511
|
#: crates/pdfs-gui/src/app/main.rs:1134 crates/pdfs-gui/src/app/main.rs:1363
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
||||||
msgid "Preferences"
|
msgid "Preferences"
|
||||||
msgstr "Preferencje"
|
msgstr "Preferencje"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1283
|
#: crates/pdfs-gui/src/app/main.rs:1135
|
||||||
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
||||||
msgid "Keyboard Shortcuts"
|
msgid "Keyboard Shortcuts"
|
||||||
msgstr "Skróty klawiszowe"
|
msgstr "Skróty klawiszowe"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1285
|
#: crates/pdfs-gui/src/app/main.rs:1137
|
||||||
msgid "About Proton Drive for Linux"
|
msgid "About Proton Drive for Linux"
|
||||||
msgstr "O programie Proton Drive for Linux"
|
msgstr "O programie Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1290
|
#: crates/pdfs-gui/src/app/main.rs:1142
|
||||||
msgid "Main menu"
|
msgid "Main menu"
|
||||||
msgstr "Menu główne"
|
msgstr "Menu główne"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1346
|
#: crates/pdfs-gui/src/app/main.rs:1198
|
||||||
msgid "Proton Drive for Linux"
|
msgid "Proton Drive for Linux"
|
||||||
msgstr "Proton Drive for Linux"
|
msgstr "Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1354
|
#: crates/pdfs-gui/src/app/main.rs:1206
|
||||||
msgid ""
|
msgid ""
|
||||||
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -166,23 +166,23 @@ msgstr ""
|
|||||||
"Nieoficjalny klient — niepowiązany z Proton AG, niepopierany ani "
|
"Nieoficjalny klient — niepowiązany z Proton AG, niepopierany ani "
|
||||||
"niewspierany przez tę firmę."
|
"niewspierany przez tę firmę."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1359
|
#: crates/pdfs-gui/src/app/main.rs:1211
|
||||||
msgid "Source Code"
|
msgid "Source Code"
|
||||||
msgstr "Kod źródłowy"
|
msgstr "Kod źródłowy"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1363
|
#: crates/pdfs-gui/src/app/main.rs:1215
|
||||||
msgid "Proton Terms of Service"
|
msgid "Proton Terms of Service"
|
||||||
msgstr "Warunki korzystania z usług Proton"
|
msgstr "Warunki korzystania z usług Proton"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1367
|
#: crates/pdfs-gui/src/app/main.rs:1219
|
||||||
msgid "Proton Privacy Policy"
|
msgid "Proton Privacy Policy"
|
||||||
msgstr "Polityka prywatności Proton"
|
msgstr "Polityka prywatności Proton"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1372
|
#: crates/pdfs-gui/src/app/main.rs:1224
|
||||||
msgid "Disclaimer"
|
msgid "Disclaimer"
|
||||||
msgstr "Zastrzeżenia"
|
msgstr "Zastrzeżenia"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1376
|
#: crates/pdfs-gui/src/app/main.rs:1228
|
||||||
msgid ""
|
msgid ""
|
||||||
"This is an unofficial, community-built client. It is not affiliated with, "
|
"This is an unofficial, community-built client. It is not affiliated with, "
|
||||||
"endorsed by, or supported by Proton AG.\n"
|
"endorsed by, or supported by Proton AG.\n"
|
||||||
@@ -206,7 +206,7 @@ msgstr ""
|
|||||||
"gwarancji. Zachowaj kopię danych, na których utratę nie możesz sobie "
|
"gwarancji. Zachowaj kopię danych, na których utratę nie możesz sobie "
|
||||||
"pozwolić."
|
"pozwolić."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1384
|
#: crates/pdfs-gui/src/app/main.rs:1236
|
||||||
msgid ""
|
msgid ""
|
||||||
"Places are named with data from <a href=\"https://www.geonames.org/"
|
"Places are named with data from <a href=\"https://www.geonames.org/"
|
||||||
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
||||||
@@ -216,7 +216,7 @@ msgstr ""
|
|||||||
"\">GeoNames</a> udostępnionych na licencji <a href=\"https://"
|
"\">GeoNames</a> udostępnionych na licencji <a href=\"https://"
|
||||||
"creativecommons.org/licenses/by/4.0/\">CC BY 4.0</a>."
|
"creativecommons.org/licenses/by/4.0/\">CC BY 4.0</a>."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1392
|
#: crates/pdfs-gui/src/app/main.rs:1244
|
||||||
msgid ""
|
msgid ""
|
||||||
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
||||||
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
||||||
@@ -226,7 +226,7 @@ msgstr ""
|
|||||||
"www.naturalearthdata.com/\">Natural Earth</a>, które należą do domeny "
|
"www.naturalearthdata.com/\">Natural Earth</a>, które należą do domeny "
|
||||||
"publicznej."
|
"publicznej."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1400
|
#: crates/pdfs-gui/src/app/main.rs:1252
|
||||||
msgid ""
|
msgid ""
|
||||||
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
||||||
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
||||||
@@ -242,15 +242,15 @@ msgstr ""
|
|||||||
|
|
||||||
#. Translators: replace with your name(s) and email address(es), one
|
#. Translators: replace with your name(s) and email address(es), one
|
||||||
#. per line, to be credited in the About dialog.
|
#. per line, to be credited in the About dialog.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1411
|
#: crates/pdfs-gui/src/app/main.rs:1263
|
||||||
msgid "translator-credits"
|
msgid "translator-credits"
|
||||||
msgstr "Współtwórcy programu Proton Drive for Linux"
|
msgstr "Współtwórcy programu Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1449
|
#: crates/pdfs-gui/src/app/main.rs:1301
|
||||||
msgid "Stop Proton Drive?"
|
msgid "Stop Proton Drive?"
|
||||||
msgstr "Zatrzymać Proton Drive?"
|
msgstr "Zatrzymać Proton Drive?"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1450
|
#: crates/pdfs-gui/src/app/main.rs:1302
|
||||||
msgid ""
|
msgid ""
|
||||||
"My files goes offline and nothing syncs until you connect again. Proton "
|
"My files goes offline and nothing syncs until you connect again. Proton "
|
||||||
"Drive starts again at your next login."
|
"Drive starts again at your next login."
|
||||||
@@ -259,7 +259,7 @@ msgstr ""
|
|||||||
"ponownego połączenia. Proton Drive uruchomi się ponownie przy następnym "
|
"ponownego połączenia. Proton Drive uruchomi się ponownie przy następnym "
|
||||||
"logowaniu."
|
"logowaniu."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1452 crates/pdfs-gui/src/app/main.rs:1727
|
#: crates/pdfs-gui/src/app/main.rs:1304 crates/pdfs-gui/src/app/main.rs:1579
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
||||||
@@ -288,30 +288,30 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Anuluj"
|
msgstr "Anuluj"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1453
|
#: crates/pdfs-gui/src/app/main.rs:1305
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Stop"
|
msgid "Stop"
|
||||||
msgstr "Zatrzymaj"
|
msgstr "Zatrzymaj"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1470
|
#: crates/pdfs-gui/src/app/main.rs:1322
|
||||||
msgid "Couldn't stop Proton Drive"
|
msgid "Couldn't stop Proton Drive"
|
||||||
msgstr "Nie można zatrzymać Proton Drive"
|
msgstr "Nie można zatrzymać Proton Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1471
|
#: crates/pdfs-gui/src/app/main.rs:1323
|
||||||
msgid "The Proton Drive service did not stop."
|
msgid "The Proton Drive service did not stop."
|
||||||
msgstr "Usługa Proton Drive nie zatrzymała się."
|
msgstr "Usługa Proton Drive nie zatrzymała się."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1507
|
#: crates/pdfs-gui/src/app/main.rs:1359
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
||||||
msgid "General"
|
msgid "General"
|
||||||
msgstr "Ogólne"
|
msgstr "Ogólne"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1509
|
#: crates/pdfs-gui/src/app/main.rs:1361
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
||||||
msgid "Search Drive"
|
msgid "Search Drive"
|
||||||
msgstr "Przeszukaj Drive"
|
msgstr "Przeszukaj Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1510
|
#: crates/pdfs-gui/src/app/main.rs:1362
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
||||||
@@ -320,38 +320,38 @@ msgstr "Przeszukaj Drive"
|
|||||||
msgid "Refresh"
|
msgid "Refresh"
|
||||||
msgstr "Odśwież"
|
msgstr "Odśwież"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1512
|
#: crates/pdfs-gui/src/app/main.rs:1364
|
||||||
msgid "Keyboard shortcuts"
|
msgid "Keyboard shortcuts"
|
||||||
msgstr "Skróty klawiszowe"
|
msgstr "Skróty klawiszowe"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1516
|
#: crates/pdfs-gui/src/app/main.rs:1368
|
||||||
msgid "Files"
|
msgid "Files"
|
||||||
msgstr "Pliki"
|
msgstr "Pliki"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1518
|
#: crates/pdfs-gui/src/app/main.rs:1370
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
||||||
msgid "New folder"
|
msgid "New folder"
|
||||||
msgstr "Nowy katalog"
|
msgstr "Nowy katalog"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1519
|
#: crates/pdfs-gui/src/app/main.rs:1371
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
||||||
msgid "Upload files"
|
msgid "Upload files"
|
||||||
msgstr "Wyślij pliki"
|
msgstr "Wyślij pliki"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1520
|
#: crates/pdfs-gui/src/app/main.rs:1372
|
||||||
msgid "Back / forward"
|
msgid "Back / forward"
|
||||||
msgstr "Wstecz/dalej"
|
msgstr "Wstecz/dalej"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1521
|
#: crates/pdfs-gui/src/app/main.rs:1373
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
||||||
msgid "Parent folder"
|
msgid "Parent folder"
|
||||||
msgstr "Katalog nadrzędny"
|
msgstr "Katalog nadrzędny"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1522
|
#: crates/pdfs-gui/src/app/main.rs:1374
|
||||||
msgid "Grid / list"
|
msgid "Grid / list"
|
||||||
msgstr "Siatka/lista"
|
msgstr "Siatka/lista"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1523
|
#: crates/pdfs-gui/src/app/main.rs:1375
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
||||||
@@ -360,7 +360,7 @@ msgstr "Siatka/lista"
|
|||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
msgstr "Zmień nazwę"
|
msgstr "Zmień nazwę"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1524 crates/pdfs-gui/src/app/main.rs:1544
|
#: crates/pdfs-gui/src/app/main.rs:1376 crates/pdfs-gui/src/app/main.rs:1396
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
||||||
@@ -370,72 +370,73 @@ msgstr "Zmień nazwę"
|
|||||||
msgid "Move to Trash"
|
msgid "Move to Trash"
|
||||||
msgstr "Przenieś do kosza"
|
msgstr "Przenieś do kosza"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1525
|
#: crates/pdfs-gui/src/app/main.rs:1377
|
||||||
msgid "Clear the selection"
|
msgid "Clear the selection"
|
||||||
msgstr "Wyczyść zaznaczenie"
|
msgstr "Wyczyść zaznaczenie"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1531
|
#: crates/pdfs-gui/src/app/main.rs:1383
|
||||||
msgid "Larger thumbnails"
|
msgid "Larger thumbnails"
|
||||||
msgstr "Większe miniatury"
|
msgstr "Większe miniatury"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1532
|
#: crates/pdfs-gui/src/app/main.rs:1384
|
||||||
msgid "Smaller thumbnails"
|
msgid "Smaller thumbnails"
|
||||||
msgstr "Mniejsze miniatury"
|
msgstr "Mniejsze miniatury"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1533
|
#: crates/pdfs-gui/src/app/main.rs:1385
|
||||||
msgid "Reset thumbnail size"
|
msgid "Reset thumbnail size"
|
||||||
msgstr "Przywróć rozmiar miniatur"
|
msgstr "Przywróć rozmiar miniatur"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1534
|
#: crates/pdfs-gui/src/app/main.rs:1386
|
||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Zaznacz wszystko"
|
msgstr "Zaznacz wszystko"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1538
|
#: crates/pdfs-gui/src/app/main.rs:1390
|
||||||
msgid "Photo Viewer"
|
msgid "Photo Viewer"
|
||||||
msgstr "Przeglądarka zdjęć"
|
msgstr "Przeglądarka zdjęć"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1540
|
#: crates/pdfs-gui/src/app/main.rs:1392
|
||||||
msgid "Previous / next photo"
|
msgid "Previous / next photo"
|
||||||
msgstr "Poprzednie/następne zdjęcie"
|
msgstr "Poprzednie/następne zdjęcie"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1541
|
#: crates/pdfs-gui/src/app/main.rs:1393
|
||||||
msgid "First / last photo"
|
msgid "First / last photo"
|
||||||
msgstr "Pierwsze/ostatnie zdjęcie"
|
msgstr "Pierwsze/ostatnie zdjęcie"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1542
|
#: crates/pdfs-gui/src/app/main.rs:1394
|
||||||
msgid "Show details"
|
msgid "Show details"
|
||||||
msgstr "Wyświetl szczegóły"
|
msgstr "Wyświetl szczegóły"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1543
|
#: crates/pdfs-gui/src/app/main.rs:1395
|
||||||
msgid "Fullscreen"
|
msgid "Fullscreen"
|
||||||
msgstr "Pełny ekran"
|
msgstr "Pełny ekran"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1545
|
#: crates/pdfs-gui/src/app/main.rs:1397
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "Zamknij"
|
msgstr "Zamknij"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1635
|
#: crates/pdfs-gui/src/app/main.rs:1487
|
||||||
msgid "Select a single item to rename it"
|
msgid "Select a single item to rename it"
|
||||||
msgstr "Należy zaznaczyć jeden element, aby zmienić jego nazwę"
|
msgstr "Należy zaznaczyć jeden element, aby zmienić jego nazwę"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1795
|
#: crates/pdfs-gui/src/app/main.rs:1647
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
||||||
msgid "Not connected"
|
msgid "Not connected"
|
||||||
msgstr "Brak połączenia"
|
msgstr "Brak połączenia"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1796
|
#: crates/pdfs-gui/src/app/main.rs:1648
|
||||||
msgid "The Proton Drive service isn't running."
|
msgid "The Proton Drive service isn't running."
|
||||||
msgstr "Usługa Proton Drive nie jest uruchomiona."
|
msgstr "Usługa Proton Drive nie jest uruchomiona."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1804
|
#: crates/pdfs-gui/src/app/main.rs:1656
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:336 crates/pdfs-gui/src/main.rs:444
|
#: crates/pdfs-gui/src/app/pages/status.rs:336
|
||||||
#: crates/pdfs-gui/src/prompt.rs:616 crates/pdfs-gui/src/prompt.rs:867
|
#: crates/pdfs-gui/src/hud/mod.rs:274 crates/pdfs-gui/src/hud/mod.rs:589
|
||||||
|
#: crates/pdfs-gui/src/main.rs:444
|
||||||
msgid "Connecting…"
|
msgid "Connecting…"
|
||||||
msgstr "Łączenie…"
|
msgstr "Łączenie…"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1805
|
#: crates/pdfs-gui/src/app/main.rs:1657
|
||||||
msgid "Waiting for the Proton Drive service to start."
|
msgid "Waiting for the Proton Drive service to start."
|
||||||
msgstr "Oczekiwanie na uruchomienie usługi Proton Drive."
|
msgstr "Oczekiwanie na uruchomienie usługi Proton Drive."
|
||||||
|
|
||||||
@@ -480,7 +481,7 @@ msgstr "Nic w ostatnim dzienniku nie pasuje do tego filtru."
|
|||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/prompt.rs:595
|
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/hud/mod.rs:245
|
||||||
msgid "Retry"
|
msgid "Retry"
|
||||||
msgstr "Ponów"
|
msgstr "Ponów"
|
||||||
|
|
||||||
@@ -1268,6 +1269,7 @@ msgstr "Usługa Proton Drive nie odpowiedziała."
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:265
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
msgstr "Otwórz"
|
msgstr "Otwórz"
|
||||||
@@ -1463,6 +1465,7 @@ msgstr "Nazwa"
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:133
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Size"
|
msgid "Size"
|
||||||
msgstr "Rozmiar"
|
msgstr "Rozmiar"
|
||||||
@@ -1647,6 +1650,7 @@ msgstr[2] "{n} plików jest teraz tylko online"
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:134
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Modified"
|
msgid "Modified"
|
||||||
msgstr "Modyfikacja"
|
msgstr "Modyfikacja"
|
||||||
@@ -1655,6 +1659,7 @@ msgstr "Modyfikacja"
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:135
|
||||||
msgid "Location"
|
msgid "Location"
|
||||||
msgstr "Położenie"
|
msgstr "Położenie"
|
||||||
|
|
||||||
@@ -1673,6 +1678,7 @@ msgstr "Odtwórz"
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:296
|
||||||
msgid "Available offline"
|
msgid "Available offline"
|
||||||
msgstr "Dostępne offline"
|
msgstr "Dostępne offline"
|
||||||
|
|
||||||
@@ -2100,7 +2106,6 @@ msgstr "Pobierz Proton Drive"
|
|||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
||||||
#: crates/pdfs-gui/src/prompt.rs:568
|
|
||||||
msgid "This computer"
|
msgid "This computer"
|
||||||
msgstr "Ten komputer"
|
msgstr "Ten komputer"
|
||||||
|
|
||||||
@@ -2595,7 +2600,7 @@ msgid "About Sync Modes"
|
|||||||
msgstr "Informacje o trybach synchronizacji"
|
msgstr "Informacje o trybach synchronizacji"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
||||||
#: crates/pdfs-gui/src/prompt.rs:60
|
#: crates/pdfs-gui/src/prompt.rs:65
|
||||||
msgid "Folders"
|
msgid "Folders"
|
||||||
msgstr "Katalogi"
|
msgstr "Katalogi"
|
||||||
|
|
||||||
@@ -3471,7 +3476,7 @@ msgid "Select Photo to Upload"
|
|||||||
msgstr "Wybór zdjęcia do wysłania"
|
msgstr "Wybór zdjęcia do wysłania"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
||||||
#: crates/pdfs-gui/src/prompt.rs:62
|
#: crates/pdfs-gui/src/prompt.rs:67
|
||||||
msgid "Images"
|
msgid "Images"
|
||||||
msgstr "Obrazy"
|
msgstr "Obrazy"
|
||||||
|
|
||||||
@@ -4654,7 +4659,7 @@ msgid "Up to date"
|
|||||||
msgstr "Aktualne"
|
msgstr "Aktualne"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
||||||
#: crates/pdfs-gui/src/prompt.rs:880 crates/pdfs-gui/src/prompt.rs:893
|
#: crates/pdfs-gui/src/hud/mod.rs:602 crates/pdfs-gui/src/hud/mod.rs:616
|
||||||
msgid "Proton Drive isn't running"
|
msgid "Proton Drive isn't running"
|
||||||
msgstr "Proton Drive nie jest uruchomiony"
|
msgstr "Proton Drive nie jest uruchomiony"
|
||||||
|
|
||||||
@@ -5208,6 +5213,7 @@ msgid "Proton Drive"
|
|||||||
msgstr "Proton Drive"
|
msgstr "Proton Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:263
|
||||||
msgid "Open folder"
|
msgid "Open folder"
|
||||||
msgstr "Otwórz katalog"
|
msgstr "Otwórz katalog"
|
||||||
|
|
||||||
@@ -5591,6 +5597,136 @@ msgstr "(brak przypiętych plików — zacznij pisać, aby wyszukać)"
|
|||||||
msgid "(cannot resolve app dirs)"
|
msgid "(cannot resolve app dirs)"
|
||||||
msgstr "(nie można ustalić katalogów aplikacji)"
|
msgstr "(nie można ustalić katalogów aplikacji)"
|
||||||
|
|
||||||
|
#. Translators: the launcher's section of recently opened files.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:57
|
||||||
|
msgid "Recent"
|
||||||
|
msgstr "Ostatnie"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:58
|
||||||
|
msgid "Pinned in Proton Drive"
|
||||||
|
msgstr "Przypięte w Proton Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:59
|
||||||
|
msgid "Best matches"
|
||||||
|
msgstr "Najlepsze wyniki"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:159
|
||||||
|
msgid "Search Proton Drive"
|
||||||
|
msgstr "Przeszukaj Proton Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:180
|
||||||
|
msgid "Search in Drive and on this computer"
|
||||||
|
msgstr "Wyszukiwanie w Drive i na tym komputerze"
|
||||||
|
|
||||||
|
#. Translators: the Escape key, shown as a hint that it closes the window.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:191
|
||||||
|
msgid "Esc"
|
||||||
|
msgstr "Esc"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:232 crates/pdfs-gui/src/hud/mod.rs:862
|
||||||
|
msgid "No results"
|
||||||
|
msgstr "Brak wyników"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:240
|
||||||
|
msgid "Proton Drive is not running"
|
||||||
|
msgstr "Proton Drive nie jest uruchomiony"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:241
|
||||||
|
msgid "Start Proton Drive to search your files."
|
||||||
|
msgstr "Uruchom Proton Drive, aby przeszukać pliki."
|
||||||
|
|
||||||
|
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:282
|
||||||
|
msgid "↑↓ navigate · ↵ open · Tab filter"
|
||||||
|
msgstr "↑↓ nawigacja · ↵ otwarcie · Tab filtr"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:864
|
||||||
|
msgid "Search your Drive"
|
||||||
|
msgstr "Przeszukaj Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:869
|
||||||
|
msgid "Still indexing this computer — local results will fill in shortly."
|
||||||
|
msgstr "Trwa indeksowanie tego komputera — lokalne wyniki wkrótce się pojawią."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:871
|
||||||
|
msgid "Try a different search, or another filter."
|
||||||
|
msgstr "Można spróbować innego wyszukiwania lub innego filtru."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:873
|
||||||
|
msgid "Start typing to search Proton Drive and the files on this computer."
|
||||||
|
msgstr ""
|
||||||
|
"Można zacząć pisać, aby przeszukać Proton Drive i pliki na tym komputerze."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:900
|
||||||
|
msgid "No pinned files"
|
||||||
|
msgstr "Brak przypiętych plików"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:901
|
||||||
|
#, rust-format
|
||||||
|
msgid "{n} pinned file"
|
||||||
|
msgid_plural "{n} pinned files"
|
||||||
|
msgstr[0] "{n} przypięty plik"
|
||||||
|
msgstr[1] "{n} przypięte pliki"
|
||||||
|
msgstr[2] "{n} przypiętych plików"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:914
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
||||||
|
msgstr "{drive} w Drive · {local} na tym komputerze · indeksowanie…"
|
||||||
|
|
||||||
|
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:919
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer"
|
||||||
|
msgstr "{drive} w Drive · {local} na tym komputerze"
|
||||||
|
|
||||||
|
#. Translators: {name} is a file name.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1050
|
||||||
|
#, rust-format
|
||||||
|
msgid "Opening {name}…"
|
||||||
|
msgstr "Otwieranie {name}…"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1090
|
||||||
|
#, rust-format
|
||||||
|
msgid "Could not open: {message}"
|
||||||
|
msgstr "Nie można otworzyć: {message}"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1094
|
||||||
|
msgid "Couldn't reach Proton Drive"
|
||||||
|
msgstr "Nie można połączyć się z Proton Drive"
|
||||||
|
|
||||||
|
#. Translators: a heading in the launcher's preview pane, followed by
|
||||||
|
#. "Available offline", "Downloaded", "Online only" or "On this computer".
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:138
|
||||||
|
msgid "Availability"
|
||||||
|
msgstr "Dostępność"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:157
|
||||||
|
msgid "Show in Folder"
|
||||||
|
msgstr "Pokaż w katalogu"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:160
|
||||||
|
msgid "Copy Path"
|
||||||
|
msgstr "Skopiuj ścieżkę"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:166
|
||||||
|
msgid "Path copied"
|
||||||
|
msgstr "Skopiowano ścieżkę"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:291
|
||||||
|
msgid "On this computer"
|
||||||
|
msgstr "Na tym komputerze"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:303
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Downloaded"
|
||||||
|
msgstr "Pobrane"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:308
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Online only"
|
||||||
|
msgstr "Tylko online"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/i18n.rs:84
|
#: crates/pdfs-gui/src/i18n.rs:84
|
||||||
msgid "Kosovo"
|
msgid "Kosovo"
|
||||||
msgstr "Kosowo"
|
msgstr "Kosowo"
|
||||||
@@ -5754,16 +5890,16 @@ msgid "Quit"
|
|||||||
msgstr "Zakończ"
|
msgstr "Zakończ"
|
||||||
|
|
||||||
#. Translators: a filter chip that shows every kind of result.
|
#. Translators: a filter chip that shows every kind of result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:59
|
#: crates/pdfs-gui/src/prompt.rs:64
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Wszystko"
|
msgstr "Wszystko"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:61
|
#: crates/pdfs-gui/src/prompt.rs:66
|
||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Dokumenty"
|
msgstr "Dokumenty"
|
||||||
|
|
||||||
#. Translators: a filter chip for audio and video files.
|
#. Translators: a filter chip for audio and video files.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:64
|
#: crates/pdfs-gui/src/prompt.rs:69
|
||||||
msgid "Media"
|
msgid "Media"
|
||||||
msgstr "Multimedia"
|
msgstr "Multimedia"
|
||||||
|
|
||||||
@@ -5772,142 +5908,49 @@ msgstr "Multimedia"
|
|||||||
msgid "My files / {path}"
|
msgid "My files / {path}"
|
||||||
msgstr "Moje pliki / {path}"
|
msgstr "Moje pliki / {path}"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:520
|
|
||||||
msgid "Search Proton Drive"
|
|
||||||
msgstr "Przeszukaj Proton Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:541
|
|
||||||
msgid "Search in Drive and on this computer"
|
|
||||||
msgstr "Wyszukiwanie w Drive i na tym komputerze"
|
|
||||||
|
|
||||||
#. Translators: the Escape key, shown as a hint that it closes the window.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:552
|
|
||||||
msgid "Esc"
|
|
||||||
msgstr "Esc"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:566 crates/pdfs-gui/src/prompt.rs:1085
|
|
||||||
msgid "Best matches"
|
|
||||||
msgstr "Najlepsze wyniki"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:582 crates/pdfs-gui/src/prompt.rs:1109
|
|
||||||
msgid "No results"
|
|
||||||
msgstr "Brak wyników"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:590
|
|
||||||
msgid "Proton Drive is not running"
|
|
||||||
msgstr "Proton Drive nie jest uruchomiony"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:591
|
|
||||||
msgid "Start Proton Drive to search your files."
|
|
||||||
msgstr "Uruchom Proton Drive, aby przeszukać pliki."
|
|
||||||
|
|
||||||
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:624
|
|
||||||
msgid "↑↓ navigate · ↵ open · Tab filter"
|
|
||||||
msgstr "↑↓ nawigacja · ↵ otwarcie · Tab filtr"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1096
|
|
||||||
msgid "Pinned in Proton Drive"
|
|
||||||
msgstr "Przypięte w Proton Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1111
|
|
||||||
msgid "Search your Drive"
|
|
||||||
msgstr "Przeszukaj Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1116
|
|
||||||
msgid "Still indexing this computer — local results will fill in shortly."
|
|
||||||
msgstr "Trwa indeksowanie tego komputera — lokalne wyniki wkrótce się pojawią."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1118
|
|
||||||
msgid "Try a different search, or another filter."
|
|
||||||
msgstr "Można spróbować innego wyszukiwania lub innego filtru."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1120
|
|
||||||
msgid "Start typing to search Proton Drive and the files on this computer."
|
|
||||||
msgstr ""
|
|
||||||
"Można zacząć pisać, aby przeszukać Proton Drive i pliki na tym komputerze."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1146
|
|
||||||
msgid "No pinned files"
|
|
||||||
msgstr "Brak przypiętych plików"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1147
|
|
||||||
#, rust-format
|
|
||||||
msgid "{n} pinned file"
|
|
||||||
msgid_plural "{n} pinned files"
|
|
||||||
msgstr[0] "{n} przypięty plik"
|
|
||||||
msgstr[1] "{n} przypięte pliki"
|
|
||||||
msgstr[2] "{n} przypiętych plików"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1156
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
|
||||||
msgstr "{drive} w Drive · {local} na tym komputerze · indeksowanie…"
|
|
||||||
|
|
||||||
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1161
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer"
|
|
||||||
msgstr "{drive} w Drive · {local} na tym komputerze"
|
|
||||||
|
|
||||||
#. Translators: {name} is a file name.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1294
|
|
||||||
#, rust-format
|
|
||||||
msgid "Opening {name}…"
|
|
||||||
msgstr "Otwieranie {name}…"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1317
|
|
||||||
#, rust-format
|
|
||||||
msgid "Could not open: {message}"
|
|
||||||
msgstr "Nie można otworzyć: {message}"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1321
|
|
||||||
msgid "Couldn't reach Proton Drive"
|
|
||||||
msgstr "Nie można połączyć się z Proton Drive"
|
|
||||||
|
|
||||||
#. Translators: the user's home folder, as the location of a result.
|
#. Translators: the user's home folder, as the location of a result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1430
|
#: crates/pdfs-gui/src/prompt.rs:455
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Katalog domowy"
|
msgstr "Katalog domowy"
|
||||||
|
|
||||||
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1432
|
#: crates/pdfs-gui/src/prompt.rs:457
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "Home / {path}"
|
msgid "Home / {path}"
|
||||||
msgstr "Katalog domowy / {path}"
|
msgstr "Katalog domowy / {path}"
|
||||||
|
|
||||||
#. Translators: the age of a file modified under a minute ago.
|
#. Translators: the age of a file modified under a minute ago.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1452
|
#: crates/pdfs-gui/src/prompt.rs:477
|
||||||
msgctxt "age"
|
msgctxt "age"
|
||||||
msgid "now"
|
msgid "now"
|
||||||
msgstr "teraz"
|
msgstr "teraz"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of minutes.
|
#. Translators: a compact file age; {count} is a number of minutes.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1454
|
#: crates/pdfs-gui/src/prompt.rs:479
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}m"
|
msgid "{count}m"
|
||||||
msgstr "{count} min"
|
msgstr "{count} min"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of hours.
|
#. Translators: a compact file age; {count} is a number of hours.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1456
|
#: crates/pdfs-gui/src/prompt.rs:481
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}h"
|
msgid "{count}h"
|
||||||
msgstr "{count} godz."
|
msgstr "{count} godz."
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of days.
|
#. Translators: a compact file age; {count} is a number of days.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1458
|
#: crates/pdfs-gui/src/prompt.rs:483
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}d"
|
msgid "{count}d"
|
||||||
msgstr "{count} dn."
|
msgstr "{count} dn."
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of months.
|
#. Translators: a compact file age; {count} is a number of months.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1460
|
#: crates/pdfs-gui/src/prompt.rs:485
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}mo"
|
msgid "{count}mo"
|
||||||
msgstr "{count} mies."
|
msgstr "{count} mies."
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of years.
|
#. Translators: a compact file age; {count} is a number of years.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1462
|
#: crates/pdfs-gui/src/prompt.rs:487
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}y"
|
msgid "{count}y"
|
||||||
msgstr "{count} r."
|
msgstr "{count} r."
|
||||||
@@ -6057,9 +6100,6 @@ msgstr "nie można połączyć się z usługą Proton Drive: {error}"
|
|||||||
#~ msgid "Use this computer's identity"
|
#~ msgid "Use this computer's identity"
|
||||||
#~ msgstr "Użycie tożsamości tego komputera"
|
#~ msgstr "Użycie tożsamości tego komputera"
|
||||||
|
|
||||||
#~ msgid "On this computer"
|
|
||||||
#~ msgstr "Na tym komputerze"
|
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
#~ "Where Proton Drive lives on this machine. Synced folders keep a full copy "
|
#~ "Where Proton Drive lives on this machine. Synced folders keep a full copy "
|
||||||
#~ "on this disk; on-demand folders keep the files in Proton Drive only and "
|
#~ "on this disk; on-demand folders keep the files in Proton Drive only and "
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: proton-drive-linux 2.1.0\n"
|
"Project-Id-Version: proton-drive-linux 2.1.0\n"
|
||||||
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
"Report-Msgid-Bugs-To: https://github.com/narrrl/proton-drive-linux/issues\n"
|
||||||
"POT-Creation-Date: 2026-09-28 16:17+0200\n"
|
"POT-Creation-Date: 2026-09-28 22:51+0200\n"
|
||||||
"PO-Revision-Date: 2026-09-23 12:00+0200\n"
|
"PO-Revision-Date: 2026-09-23 12:00+0200\n"
|
||||||
"Last-Translator: Proton Drive for Linux contributors\n"
|
"Last-Translator: Proton Drive for Linux contributors\n"
|
||||||
"Language-Team: Portuguese (Brazil)\n"
|
"Language-Team: Portuguese (Brazil)\n"
|
||||||
@@ -57,7 +57,7 @@ msgstr "%-d de %b de %Y"
|
|||||||
msgid "%A, %B %-d, %Y, %H:%M"
|
msgid "%A, %B %-d, %Y, %H:%M"
|
||||||
msgstr "%A, %-d de %B de %Y, %H:%M"
|
msgstr "%A, %-d de %B de %Y, %H:%M"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:882
|
#: crates/pdfs-gui/src/app/main.rs:734
|
||||||
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
#: crates/pdfs-gui/src/app/pages/activity_text.rs:466
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
#: crates/pdfs-gui/src/app/pages/browser.rs:454
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
#: crates/pdfs-gui/src/app/pages/browser.rs:819
|
||||||
@@ -68,91 +68,91 @@ msgstr "%A, %-d de %B de %Y, %H:%M"
|
|||||||
msgid "My files"
|
msgid "My files"
|
||||||
msgstr "Meus arquivos"
|
msgstr "Meus arquivos"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:885 crates/pdfs-gui/src/app/main.rs:1529
|
#: crates/pdfs-gui/src/app/main.rs:737 crates/pdfs-gui/src/app/main.rs:1381
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
#: crates/pdfs-gui/src/app/pages/albums.rs:638
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
#: crates/pdfs-gui/src/app/pages/photos.rs:842
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
#: crates/pdfs-gui/src/app/pages/places.rs:441
|
||||||
msgid "Photos"
|
msgid "Photos"
|
||||||
msgstr "Fotos"
|
msgstr "Fotos"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:890
|
#: crates/pdfs-gui/src/app/main.rs:742
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
#: crates/pdfs-gui/src/app/pages/shared.rs:168
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
#: crates/pdfs-gui/src/app/pages/shared.rs:482
|
||||||
msgid "Shared with me"
|
msgid "Shared with me"
|
||||||
msgstr "Compartilhados comigo"
|
msgstr "Compartilhados comigo"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:895
|
#: crates/pdfs-gui/src/app/main.rs:747
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:120
|
||||||
msgid "Shared by me"
|
msgid "Shared by me"
|
||||||
msgstr "Compartilhados por mim"
|
msgstr "Compartilhados por mim"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:898
|
#: crates/pdfs-gui/src/app/main.rs:750
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
#: crates/pdfs-gui/src/app/pages/devices.rs:140
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
#: crates/pdfs-gui/src/app/pages/devices.rs:481
|
||||||
msgid "Computers"
|
msgid "Computers"
|
||||||
msgstr "Computadores"
|
msgstr "Computadores"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:899
|
#: crates/pdfs-gui/src/app/main.rs:751
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
#: crates/pdfs-gui/src/app/pages/trash.rs:101
|
||||||
msgid "Trash"
|
msgid "Trash"
|
||||||
msgstr "Lixeira"
|
msgstr "Lixeira"
|
||||||
|
|
||||||
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
#. Translators: sidebar entry for the page listing this computer's synced folders.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:901
|
#: crates/pdfs-gui/src/app/main.rs:753
|
||||||
msgid "Sync"
|
msgid "Sync"
|
||||||
msgstr "Sincronização"
|
msgstr "Sincronização"
|
||||||
|
|
||||||
#. Translators: a failed action's toast. {what} says what failed, such as
|
#. Translators: a failed action's toast. {what} says what failed, such as
|
||||||
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
#. "Couldn't rename"; {detail} is the reason, usually untranslated text
|
||||||
#. from the mount service.
|
#. from the mount service.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1218
|
#: crates/pdfs-gui/src/app/main.rs:1070
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{what}: {detail}"
|
msgid "{what}: {detail}"
|
||||||
msgstr "{what}: {detail}"
|
msgstr "{what}: {detail}"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1237
|
#: crates/pdfs-gui/src/app/main.rs:1089
|
||||||
msgid "You're offline"
|
msgid "You're offline"
|
||||||
msgstr "Você está offline"
|
msgstr "Você está offline"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1238
|
#: crates/pdfs-gui/src/app/main.rs:1090
|
||||||
msgid "That's not there any more"
|
msgid "That's not there any more"
|
||||||
msgstr "Isso não existe mais"
|
msgstr "Isso não existe mais"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1239
|
#: crates/pdfs-gui/src/app/main.rs:1091
|
||||||
msgid "You don't have access to that"
|
msgid "You don't have access to that"
|
||||||
msgstr "Você não tem acesso a isso"
|
msgstr "Você não tem acesso a isso"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1240
|
#: crates/pdfs-gui/src/app/main.rs:1092
|
||||||
msgid "Something changed this first"
|
msgid "Something changed this first"
|
||||||
msgstr "Algo alterou isso antes"
|
msgstr "Algo alterou isso antes"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1241
|
#: crates/pdfs-gui/src/app/main.rs:1093
|
||||||
msgid "Your Proton Drive is full"
|
msgid "Your Proton Drive is full"
|
||||||
msgstr "Seu Proton Drive está cheio"
|
msgstr "Seu Proton Drive está cheio"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1282 crates/pdfs-gui/src/app/main.rs:1511
|
#: crates/pdfs-gui/src/app/main.rs:1134 crates/pdfs-gui/src/app/main.rs:1363
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
#: crates/pdfs-gui/src/app/pages/status.rs:385
|
||||||
msgid "Preferences"
|
msgid "Preferences"
|
||||||
msgstr "Preferências"
|
msgstr "Preferências"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1283
|
#: crates/pdfs-gui/src/app/main.rs:1135
|
||||||
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
#: crates/pdfs-gui/src/app/widgets/compat.rs:262
|
||||||
msgid "Keyboard Shortcuts"
|
msgid "Keyboard Shortcuts"
|
||||||
msgstr "Atalhos de teclado"
|
msgstr "Atalhos de teclado"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1285
|
#: crates/pdfs-gui/src/app/main.rs:1137
|
||||||
msgid "About Proton Drive for Linux"
|
msgid "About Proton Drive for Linux"
|
||||||
msgstr "Sobre o Proton Drive for Linux"
|
msgstr "Sobre o Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1290
|
#: crates/pdfs-gui/src/app/main.rs:1142
|
||||||
msgid "Main menu"
|
msgid "Main menu"
|
||||||
msgstr "Menu principal"
|
msgstr "Menu principal"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1346
|
#: crates/pdfs-gui/src/app/main.rs:1198
|
||||||
msgid "Proton Drive for Linux"
|
msgid "Proton Drive for Linux"
|
||||||
msgstr "Proton Drive for Linux"
|
msgstr "Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1354
|
#: crates/pdfs-gui/src/app/main.rs:1206
|
||||||
msgid ""
|
msgid ""
|
||||||
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
"Files-on-demand Proton Drive for the Linux desktop.\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -163,23 +163,23 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Cliente não oficial — sem afiliação, endosso ou suporte da Proton AG."
|
"Cliente não oficial — sem afiliação, endosso ou suporte da Proton AG."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1359
|
#: crates/pdfs-gui/src/app/main.rs:1211
|
||||||
msgid "Source Code"
|
msgid "Source Code"
|
||||||
msgstr "Código-fonte"
|
msgstr "Código-fonte"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1363
|
#: crates/pdfs-gui/src/app/main.rs:1215
|
||||||
msgid "Proton Terms of Service"
|
msgid "Proton Terms of Service"
|
||||||
msgstr "Termos de Serviço da Proton"
|
msgstr "Termos de Serviço da Proton"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1367
|
#: crates/pdfs-gui/src/app/main.rs:1219
|
||||||
msgid "Proton Privacy Policy"
|
msgid "Proton Privacy Policy"
|
||||||
msgstr "Política de Privacidade da Proton"
|
msgstr "Política de Privacidade da Proton"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1372
|
#: crates/pdfs-gui/src/app/main.rs:1224
|
||||||
msgid "Disclaimer"
|
msgid "Disclaimer"
|
||||||
msgstr "Aviso legal"
|
msgstr "Aviso legal"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1376
|
#: crates/pdfs-gui/src/app/main.rs:1228
|
||||||
msgid ""
|
msgid ""
|
||||||
"This is an unofficial, community-built client. It is not affiliated with, "
|
"This is an unofficial, community-built client. It is not affiliated with, "
|
||||||
"endorsed by, or supported by Proton AG.\n"
|
"endorsed by, or supported by Proton AG.\n"
|
||||||
@@ -202,7 +202,7 @@ msgstr ""
|
|||||||
"Este software é fornecido “no estado em que se encontra”, sem garantia de "
|
"Este software é fornecido “no estado em que se encontra”, sem garantia de "
|
||||||
"qualquer tipo. Mantenha uma cópia de todos os dados que você não pode perder."
|
"qualquer tipo. Mantenha uma cópia de todos os dados que você não pode perder."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1384
|
#: crates/pdfs-gui/src/app/main.rs:1236
|
||||||
msgid ""
|
msgid ""
|
||||||
"Places are named with data from <a href=\"https://www.geonames.org/"
|
"Places are named with data from <a href=\"https://www.geonames.org/"
|
||||||
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
"\">GeoNames</a>, licensed under <a href=\"https://creativecommons.org/"
|
||||||
@@ -212,7 +212,7 @@ msgstr ""
|
|||||||
"\">GeoNames</a>, licenciados sob <a href=\"https://creativecommons.org/"
|
"\">GeoNames</a>, licenciados sob <a href=\"https://creativecommons.org/"
|
||||||
"licenses/by/4.0/\">CC BY 4.0</a>."
|
"licenses/by/4.0/\">CC BY 4.0</a>."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1392
|
#: crates/pdfs-gui/src/app/main.rs:1244
|
||||||
msgid ""
|
msgid ""
|
||||||
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
"The places map is drawn from <a href=\"https://www.naturalearthdata.com/"
|
||||||
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
"\">Natural Earth</a> land outlines and country borders, which are in the "
|
||||||
@@ -222,7 +222,7 @@ msgstr ""
|
|||||||
"fronteiras de países do <a href=\"https://www.naturalearthdata.com/"
|
"fronteiras de países do <a href=\"https://www.naturalearthdata.com/"
|
||||||
"\">Natural Earth</a>, que são de domínio público."
|
"\">Natural Earth</a>, que são de domínio público."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1400
|
#: crates/pdfs-gui/src/app/main.rs:1252
|
||||||
msgid ""
|
msgid ""
|
||||||
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
"The street map, when turned on, comes from <a href=\"https://openfreemap.org/"
|
||||||
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
"\">OpenFreeMap</a> in the <a href=\"https://www.openmaptiles.org/"
|
||||||
@@ -238,15 +238,15 @@ msgstr ""
|
|||||||
|
|
||||||
#. Translators: replace with your name(s) and email address(es), one
|
#. Translators: replace with your name(s) and email address(es), one
|
||||||
#. per line, to be credited in the About dialog.
|
#. per line, to be credited in the About dialog.
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1411
|
#: crates/pdfs-gui/src/app/main.rs:1263
|
||||||
msgid "translator-credits"
|
msgid "translator-credits"
|
||||||
msgstr "Colaboradores do Proton Drive for Linux"
|
msgstr "Colaboradores do Proton Drive for Linux"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1449
|
#: crates/pdfs-gui/src/app/main.rs:1301
|
||||||
msgid "Stop Proton Drive?"
|
msgid "Stop Proton Drive?"
|
||||||
msgstr "Parar o Proton Drive?"
|
msgstr "Parar o Proton Drive?"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1450
|
#: crates/pdfs-gui/src/app/main.rs:1302
|
||||||
msgid ""
|
msgid ""
|
||||||
"My files goes offline and nothing syncs until you connect again. Proton "
|
"My files goes offline and nothing syncs until you connect again. Proton "
|
||||||
"Drive starts again at your next login."
|
"Drive starts again at your next login."
|
||||||
@@ -254,7 +254,7 @@ msgstr ""
|
|||||||
"Meus arquivos fica offline e nada é sincronizado até você se conectar "
|
"Meus arquivos fica offline e nada é sincronizado até você se conectar "
|
||||||
"novamente. O Proton Drive será iniciado de novo no seu próximo login."
|
"novamente. O Proton Drive será iniciado de novo no seu próximo login."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1452 crates/pdfs-gui/src/app/main.rs:1727
|
#: crates/pdfs-gui/src/app/main.rs:1304 crates/pdfs-gui/src/app/main.rs:1579
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
#: crates/pdfs-gui/src/app/pages/albums.rs:243
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
#: crates/pdfs-gui/src/app/pages/albums.rs:428
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1892
|
||||||
@@ -283,30 +283,30 @@ msgstr ""
|
|||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Cancelar"
|
msgstr "Cancelar"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1453
|
#: crates/pdfs-gui/src/app/main.rs:1305
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Stop"
|
msgid "Stop"
|
||||||
msgstr "Parar"
|
msgstr "Parar"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1470
|
#: crates/pdfs-gui/src/app/main.rs:1322
|
||||||
msgid "Couldn't stop Proton Drive"
|
msgid "Couldn't stop Proton Drive"
|
||||||
msgstr "Não foi possível parar o Proton Drive"
|
msgstr "Não foi possível parar o Proton Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1471
|
#: crates/pdfs-gui/src/app/main.rs:1323
|
||||||
msgid "The Proton Drive service did not stop."
|
msgid "The Proton Drive service did not stop."
|
||||||
msgstr "O serviço do Proton Drive não parou."
|
msgstr "O serviço do Proton Drive não parou."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1507
|
#: crates/pdfs-gui/src/app/main.rs:1359
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
#: crates/pdfs-gui/src/app/pages/status.rs:254
|
||||||
msgid "General"
|
msgid "General"
|
||||||
msgstr "Geral"
|
msgstr "Geral"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1509
|
#: crates/pdfs-gui/src/app/main.rs:1361
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
#: crates/pdfs-gui/src/app/pages/browser.rs:247
|
||||||
msgid "Search Drive"
|
msgid "Search Drive"
|
||||||
msgstr "Pesquisar no Drive"
|
msgstr "Pesquisar no Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1510
|
#: crates/pdfs-gui/src/app/main.rs:1362
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
#: crates/pdfs-gui/src/app/pages/browser.rs:957
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
#: crates/pdfs-gui/src/app/pages/devices.rs:554
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
#: crates/pdfs-gui/src/app/pages/shared.rs:586
|
||||||
@@ -315,38 +315,38 @@ msgstr "Pesquisar no Drive"
|
|||||||
msgid "Refresh"
|
msgid "Refresh"
|
||||||
msgstr "Atualizar"
|
msgstr "Atualizar"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1512
|
#: crates/pdfs-gui/src/app/main.rs:1364
|
||||||
msgid "Keyboard shortcuts"
|
msgid "Keyboard shortcuts"
|
||||||
msgstr "Atalhos de teclado"
|
msgstr "Atalhos de teclado"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1516
|
#: crates/pdfs-gui/src/app/main.rs:1368
|
||||||
msgid "Files"
|
msgid "Files"
|
||||||
msgstr "Arquivos"
|
msgstr "Arquivos"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1518
|
#: crates/pdfs-gui/src/app/main.rs:1370
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
#: crates/pdfs-gui/src/app/pages/browser.rs:294
|
||||||
msgid "New folder"
|
msgid "New folder"
|
||||||
msgstr "Nova pasta"
|
msgstr "Nova pasta"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1519
|
#: crates/pdfs-gui/src/app/main.rs:1371
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
#: crates/pdfs-gui/src/app/pages/browser.rs:290
|
||||||
msgid "Upload files"
|
msgid "Upload files"
|
||||||
msgstr "Enviar arquivos"
|
msgstr "Enviar arquivos"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1520
|
#: crates/pdfs-gui/src/app/main.rs:1372
|
||||||
msgid "Back / forward"
|
msgid "Back / forward"
|
||||||
msgstr "Voltar / avançar"
|
msgstr "Voltar / avançar"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1521
|
#: crates/pdfs-gui/src/app/main.rs:1373
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1939
|
||||||
msgid "Parent folder"
|
msgid "Parent folder"
|
||||||
msgstr "Pasta superior"
|
msgstr "Pasta superior"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1522
|
#: crates/pdfs-gui/src/app/main.rs:1374
|
||||||
msgid "Grid / list"
|
msgid "Grid / list"
|
||||||
msgstr "Grade / lista"
|
msgstr "Grade / lista"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1523
|
#: crates/pdfs-gui/src/app/main.rs:1375
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
#: crates/pdfs-gui/src/app/pages/albums.rs:298
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1871
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
#: crates/pdfs-gui/src/app/pages/browser.rs:1893
|
||||||
@@ -355,7 +355,7 @@ msgstr "Grade / lista"
|
|||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
msgstr "Renomear"
|
msgstr "Renomear"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1524 crates/pdfs-gui/src/app/main.rs:1544
|
#: crates/pdfs-gui/src/app/main.rs:1376 crates/pdfs-gui/src/app/main.rs:1396
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
#: crates/pdfs-gui/src/app/pages/browser.rs:357
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
#: crates/pdfs-gui/src/app/pages/browser.rs:900
|
||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
#: crates/pdfs-gui/src/app/pages/browser.rs:932
|
||||||
@@ -365,72 +365,73 @@ msgstr "Renomear"
|
|||||||
msgid "Move to Trash"
|
msgid "Move to Trash"
|
||||||
msgstr "Mover para a lixeira"
|
msgstr "Mover para a lixeira"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1525
|
#: crates/pdfs-gui/src/app/main.rs:1377
|
||||||
msgid "Clear the selection"
|
msgid "Clear the selection"
|
||||||
msgstr "Limpar a seleção"
|
msgstr "Limpar a seleção"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1531
|
#: crates/pdfs-gui/src/app/main.rs:1383
|
||||||
msgid "Larger thumbnails"
|
msgid "Larger thumbnails"
|
||||||
msgstr "Miniaturas maiores"
|
msgstr "Miniaturas maiores"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1532
|
#: crates/pdfs-gui/src/app/main.rs:1384
|
||||||
msgid "Smaller thumbnails"
|
msgid "Smaller thumbnails"
|
||||||
msgstr "Miniaturas menores"
|
msgstr "Miniaturas menores"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1533
|
#: crates/pdfs-gui/src/app/main.rs:1385
|
||||||
msgid "Reset thumbnail size"
|
msgid "Reset thumbnail size"
|
||||||
msgstr "Redefinir tamanho das miniaturas"
|
msgstr "Redefinir tamanho das miniaturas"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1534
|
#: crates/pdfs-gui/src/app/main.rs:1386
|
||||||
msgid "Select all"
|
msgid "Select all"
|
||||||
msgstr "Selecionar tudo"
|
msgstr "Selecionar tudo"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1538
|
#: crates/pdfs-gui/src/app/main.rs:1390
|
||||||
msgid "Photo Viewer"
|
msgid "Photo Viewer"
|
||||||
msgstr "Visualizador de fotos"
|
msgstr "Visualizador de fotos"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1540
|
#: crates/pdfs-gui/src/app/main.rs:1392
|
||||||
msgid "Previous / next photo"
|
msgid "Previous / next photo"
|
||||||
msgstr "Foto anterior / próxima"
|
msgstr "Foto anterior / próxima"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1541
|
#: crates/pdfs-gui/src/app/main.rs:1393
|
||||||
msgid "First / last photo"
|
msgid "First / last photo"
|
||||||
msgstr "Primeira / última foto"
|
msgstr "Primeira / última foto"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1542
|
#: crates/pdfs-gui/src/app/main.rs:1394
|
||||||
msgid "Show details"
|
msgid "Show details"
|
||||||
msgstr "Mostrar detalhes"
|
msgstr "Mostrar detalhes"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1543
|
#: crates/pdfs-gui/src/app/main.rs:1395
|
||||||
msgid "Fullscreen"
|
msgid "Fullscreen"
|
||||||
msgstr "Tela cheia"
|
msgstr "Tela cheia"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1545
|
#: crates/pdfs-gui/src/app/main.rs:1397
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "Fechar"
|
msgstr "Fechar"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1635
|
#: crates/pdfs-gui/src/app/main.rs:1487
|
||||||
msgid "Select a single item to rename it"
|
msgid "Select a single item to rename it"
|
||||||
msgstr "Selecione um único item para renomeá-lo"
|
msgstr "Selecione um único item para renomeá-lo"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1795
|
#: crates/pdfs-gui/src/app/main.rs:1647
|
||||||
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
#: crates/pdfs-gui/src/app/pages/albums.rs:85
|
||||||
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
#: crates/pdfs-gui/src/app/pages/places.rs:104
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
#: crates/pdfs-gui/src/app/pages/status.rs:1366
|
||||||
msgid "Not connected"
|
msgid "Not connected"
|
||||||
msgstr "Não conectado"
|
msgstr "Não conectado"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1796
|
#: crates/pdfs-gui/src/app/main.rs:1648
|
||||||
msgid "The Proton Drive service isn't running."
|
msgid "The Proton Drive service isn't running."
|
||||||
msgstr "O serviço do Proton Drive não está em execução."
|
msgstr "O serviço do Proton Drive não está em execução."
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1804
|
#: crates/pdfs-gui/src/app/main.rs:1656
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:336 crates/pdfs-gui/src/main.rs:444
|
#: crates/pdfs-gui/src/app/pages/status.rs:336
|
||||||
#: crates/pdfs-gui/src/prompt.rs:616 crates/pdfs-gui/src/prompt.rs:867
|
#: crates/pdfs-gui/src/hud/mod.rs:274 crates/pdfs-gui/src/hud/mod.rs:589
|
||||||
|
#: crates/pdfs-gui/src/main.rs:444
|
||||||
msgid "Connecting…"
|
msgid "Connecting…"
|
||||||
msgstr "Conectando…"
|
msgstr "Conectando…"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/main.rs:1805
|
#: crates/pdfs-gui/src/app/main.rs:1657
|
||||||
msgid "Waiting for the Proton Drive service to start."
|
msgid "Waiting for the Proton Drive service to start."
|
||||||
msgstr "Aguardando o serviço do Proton Drive iniciar."
|
msgstr "Aguardando o serviço do Proton Drive iniciar."
|
||||||
|
|
||||||
@@ -475,7 +476,7 @@ msgstr "Nada no registro recente corresponde a este filtro."
|
|||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
#: crates/pdfs-gui/src/app/pages/photos.rs:465
|
||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
#: crates/pdfs-gui/src/app/pages/shared.rs:183
|
||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:94
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/prompt.rs:595
|
#: crates/pdfs-gui/src/app/pages/trash.rs:53 crates/pdfs-gui/src/hud/mod.rs:245
|
||||||
msgid "Retry"
|
msgid "Retry"
|
||||||
msgstr "Tentar novamente"
|
msgstr "Tentar novamente"
|
||||||
|
|
||||||
@@ -1234,6 +1235,7 @@ msgstr "O serviço do Proton Drive não respondeu."
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:474
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
#: crates/pdfs-gui/src/app/widgets/details.rs:155
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
#: crates/pdfs-gui/src/app/widgets/details.rs:395
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:265
|
||||||
msgctxt "verb"
|
msgctxt "verb"
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
msgstr "Abrir"
|
msgstr "Abrir"
|
||||||
@@ -1424,6 +1426,7 @@ msgstr "Nome"
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
#: crates/pdfs-gui/src/app/pages/shared.rs:273
|
||||||
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
#: crates/pdfs-gui/src/app/pages/trash.rs:153
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
#: crates/pdfs-gui/src/app/widgets/details.rs:88
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:133
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Size"
|
msgid "Size"
|
||||||
msgstr "Tamanho"
|
msgstr "Tamanho"
|
||||||
@@ -1598,6 +1601,7 @@ msgstr[1] "{n} arquivos agora estão somente online"
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
#: crates/pdfs-gui/src/app/pages/browser.rs:803
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
#: crates/pdfs-gui/src/app/pages/devices.rs:175
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
#: crates/pdfs-gui/src/app/widgets/details.rs:93
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:134
|
||||||
msgctxt "column"
|
msgctxt "column"
|
||||||
msgid "Modified"
|
msgid "Modified"
|
||||||
msgstr "Modificado"
|
msgstr "Modificado"
|
||||||
@@ -1606,6 +1610,7 @@ msgstr "Modificado"
|
|||||||
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
#: crates/pdfs-gui/src/app/pages/shared_by_me.rs:154
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
#: crates/pdfs-gui/src/app/pages/status.rs:192
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
#: crates/pdfs-gui/src/app/widgets/details.rs:98
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:135
|
||||||
msgid "Location"
|
msgid "Location"
|
||||||
msgstr "Local"
|
msgstr "Local"
|
||||||
|
|
||||||
@@ -1624,6 +1629,7 @@ msgstr "Reproduzir"
|
|||||||
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
#: crates/pdfs-gui/src/app/pages/browser.rs:916
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
#: crates/pdfs-gui/src/app/pages/status.rs:311
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
#: crates/pdfs-gui/src/app/widgets/details.rs:111
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:296
|
||||||
msgid "Available offline"
|
msgid "Available offline"
|
||||||
msgstr "Disponível offline"
|
msgstr "Disponível offline"
|
||||||
|
|
||||||
@@ -2043,7 +2049,6 @@ msgstr "Baixar o Proton Drive"
|
|||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
#: crates/pdfs-gui/src/app/pages/devices.rs:391
|
||||||
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
#: crates/pdfs-gui/src/app/pages/devices.rs:397
|
||||||
#: crates/pdfs-gui/src/prompt.rs:568
|
|
||||||
msgid "This computer"
|
msgid "This computer"
|
||||||
msgstr "Este computador"
|
msgstr "Este computador"
|
||||||
|
|
||||||
@@ -2531,7 +2536,7 @@ msgid "About Sync Modes"
|
|||||||
msgstr "Sobre os modos de sincronização"
|
msgstr "Sobre os modos de sincronização"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
#: crates/pdfs-gui/src/app/pages/locations.rs:178
|
||||||
#: crates/pdfs-gui/src/prompt.rs:60
|
#: crates/pdfs-gui/src/prompt.rs:65
|
||||||
msgid "Folders"
|
msgid "Folders"
|
||||||
msgstr "Pastas"
|
msgstr "Pastas"
|
||||||
|
|
||||||
@@ -3401,7 +3406,7 @@ msgid "Select Photo to Upload"
|
|||||||
msgstr "Selecione a foto a enviar"
|
msgstr "Selecione a foto a enviar"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
#: crates/pdfs-gui/src/app/pages/photos.rs:1350
|
||||||
#: crates/pdfs-gui/src/prompt.rs:62
|
#: crates/pdfs-gui/src/prompt.rs:67
|
||||||
msgid "Images"
|
msgid "Images"
|
||||||
msgstr "Imagens"
|
msgstr "Imagens"
|
||||||
|
|
||||||
@@ -4563,7 +4568,7 @@ msgid "Up to date"
|
|||||||
msgstr "Atualizado"
|
msgstr "Atualizado"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
#: crates/pdfs-gui/src/app/pages/status.rs:1367
|
||||||
#: crates/pdfs-gui/src/prompt.rs:880 crates/pdfs-gui/src/prompt.rs:893
|
#: crates/pdfs-gui/src/hud/mod.rs:602 crates/pdfs-gui/src/hud/mod.rs:616
|
||||||
msgid "Proton Drive isn't running"
|
msgid "Proton Drive isn't running"
|
||||||
msgstr "O Proton Drive não está em execução"
|
msgstr "O Proton Drive não está em execução"
|
||||||
|
|
||||||
@@ -5106,6 +5111,7 @@ msgid "Proton Drive"
|
|||||||
msgstr "Proton Drive"
|
msgstr "Proton Drive"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
#: crates/pdfs-gui/src/app/widgets/details.rs:393
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:263
|
||||||
msgid "Open folder"
|
msgid "Open folder"
|
||||||
msgstr "Abrir pasta"
|
msgstr "Abrir pasta"
|
||||||
|
|
||||||
@@ -5488,6 +5494,137 @@ msgstr "(nenhum arquivo fixado — digite para pesquisar)"
|
|||||||
msgid "(cannot resolve app dirs)"
|
msgid "(cannot resolve app dirs)"
|
||||||
msgstr "(não foi possível resolver os diretórios do aplicativo)"
|
msgstr "(não foi possível resolver os diretórios do aplicativo)"
|
||||||
|
|
||||||
|
#. Translators: the launcher's section of recently opened files.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:57
|
||||||
|
msgid "Recent"
|
||||||
|
msgstr "Recentes"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:58
|
||||||
|
msgid "Pinned in Proton Drive"
|
||||||
|
msgstr "Fixado no Proton Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:59
|
||||||
|
msgid "Best matches"
|
||||||
|
msgstr "Melhores correspondências"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:159
|
||||||
|
msgid "Search Proton Drive"
|
||||||
|
msgstr "Pesquisar no Proton Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:180
|
||||||
|
msgid "Search in Drive and on this computer"
|
||||||
|
msgstr "Pesquisar no Drive e neste computador"
|
||||||
|
|
||||||
|
#. Translators: the Escape key, shown as a hint that it closes the window.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:191
|
||||||
|
msgid "Esc"
|
||||||
|
msgstr "Esc"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:232 crates/pdfs-gui/src/hud/mod.rs:862
|
||||||
|
msgid "No results"
|
||||||
|
msgstr "Nenhum resultado"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:240
|
||||||
|
msgid "Proton Drive is not running"
|
||||||
|
msgstr "O Proton Drive não está em execução"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:241
|
||||||
|
msgid "Start Proton Drive to search your files."
|
||||||
|
msgstr "Inicie o Proton Drive para pesquisar seus arquivos."
|
||||||
|
|
||||||
|
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:282
|
||||||
|
msgid "↑↓ navigate · ↵ open · Tab filter"
|
||||||
|
msgstr "↑↓ navegar · ↵ abrir · Tab filtrar"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:864
|
||||||
|
msgid "Search your Drive"
|
||||||
|
msgstr "Pesquisar no seu Drive"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:869
|
||||||
|
msgid "Still indexing this computer — local results will fill in shortly."
|
||||||
|
msgstr ""
|
||||||
|
"Ainda indexando este computador — os resultados locais aparecerão em breve."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:871
|
||||||
|
msgid "Try a different search, or another filter."
|
||||||
|
msgstr "Tente uma pesquisa diferente ou outro filtro."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:873
|
||||||
|
msgid "Start typing to search Proton Drive and the files on this computer."
|
||||||
|
msgstr ""
|
||||||
|
"Comece a digitar para pesquisar no Proton Drive e nos arquivos deste "
|
||||||
|
"computador."
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:900
|
||||||
|
msgid "No pinned files"
|
||||||
|
msgstr "Nenhum arquivo fixado"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:901
|
||||||
|
#, rust-format
|
||||||
|
msgid "{n} pinned file"
|
||||||
|
msgid_plural "{n} pinned files"
|
||||||
|
msgstr[0] "{n} arquivo fixado"
|
||||||
|
msgstr[1] "{n} arquivos fixados"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:914
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
||||||
|
msgstr "{drive} no Drive · {local} neste computador · indexando…"
|
||||||
|
|
||||||
|
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:919
|
||||||
|
#, rust-format
|
||||||
|
msgid "{drive} in Drive · {local} on this computer"
|
||||||
|
msgstr "{drive} no Drive · {local} neste computador"
|
||||||
|
|
||||||
|
#. Translators: {name} is a file name.
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1050
|
||||||
|
#, rust-format
|
||||||
|
msgid "Opening {name}…"
|
||||||
|
msgstr "Abrindo {name}…"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1090
|
||||||
|
#, rust-format
|
||||||
|
msgid "Could not open: {message}"
|
||||||
|
msgstr "Não foi possível abrir: {message}"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/mod.rs:1094
|
||||||
|
msgid "Couldn't reach Proton Drive"
|
||||||
|
msgstr "Não foi possível acessar o Proton Drive"
|
||||||
|
|
||||||
|
#. Translators: a heading in the launcher's preview pane, followed by
|
||||||
|
#. "Available offline", "Downloaded", "Online only" or "On this computer".
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:138
|
||||||
|
msgid "Availability"
|
||||||
|
msgstr "Disponibilidade"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:157
|
||||||
|
msgid "Show in Folder"
|
||||||
|
msgstr "Mostrar na pasta"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:160
|
||||||
|
msgid "Copy Path"
|
||||||
|
msgstr "Copiar caminho"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:166
|
||||||
|
msgid "Path copied"
|
||||||
|
msgstr "Caminho copiado"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:291
|
||||||
|
msgid "On this computer"
|
||||||
|
msgstr "Neste computador"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:303
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Downloaded"
|
||||||
|
msgstr "Baixado"
|
||||||
|
|
||||||
|
#: crates/pdfs-gui/src/hud/preview.rs:308
|
||||||
|
msgctxt "file state"
|
||||||
|
msgid "Online only"
|
||||||
|
msgstr "Somente online"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/i18n.rs:84
|
#: crates/pdfs-gui/src/i18n.rs:84
|
||||||
msgid "Kosovo"
|
msgid "Kosovo"
|
||||||
msgstr "Kosovo"
|
msgstr "Kosovo"
|
||||||
@@ -5641,16 +5778,16 @@ msgid "Quit"
|
|||||||
msgstr "Sair"
|
msgstr "Sair"
|
||||||
|
|
||||||
#. Translators: a filter chip that shows every kind of result.
|
#. Translators: a filter chip that shows every kind of result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:59
|
#: crates/pdfs-gui/src/prompt.rs:64
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Tudo"
|
msgstr "Tudo"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:61
|
#: crates/pdfs-gui/src/prompt.rs:66
|
||||||
msgid "Documents"
|
msgid "Documents"
|
||||||
msgstr "Documentos"
|
msgstr "Documentos"
|
||||||
|
|
||||||
#. Translators: a filter chip for audio and video files.
|
#. Translators: a filter chip for audio and video files.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:64
|
#: crates/pdfs-gui/src/prompt.rs:69
|
||||||
msgid "Media"
|
msgid "Media"
|
||||||
msgstr "Mídia"
|
msgstr "Mídia"
|
||||||
|
|
||||||
@@ -5659,143 +5796,49 @@ msgstr "Mídia"
|
|||||||
msgid "My files / {path}"
|
msgid "My files / {path}"
|
||||||
msgstr "Meus arquivos / {path}"
|
msgstr "Meus arquivos / {path}"
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:520
|
|
||||||
msgid "Search Proton Drive"
|
|
||||||
msgstr "Pesquisar no Proton Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:541
|
|
||||||
msgid "Search in Drive and on this computer"
|
|
||||||
msgstr "Pesquisar no Drive e neste computador"
|
|
||||||
|
|
||||||
#. Translators: the Escape key, shown as a hint that it closes the window.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:552
|
|
||||||
msgid "Esc"
|
|
||||||
msgstr "Esc"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:566 crates/pdfs-gui/src/prompt.rs:1085
|
|
||||||
msgid "Best matches"
|
|
||||||
msgstr "Melhores correspondências"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:582 crates/pdfs-gui/src/prompt.rs:1109
|
|
||||||
msgid "No results"
|
|
||||||
msgstr "Nenhum resultado"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:590
|
|
||||||
msgid "Proton Drive is not running"
|
|
||||||
msgstr "O Proton Drive não está em execução"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:591
|
|
||||||
msgid "Start Proton Drive to search your files."
|
|
||||||
msgstr "Inicie o Proton Drive para pesquisar seus arquivos."
|
|
||||||
|
|
||||||
#. Translators: keyboard hints in the footer; keep the arrow and Enter symbols.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:624
|
|
||||||
msgid "↑↓ navigate · ↵ open · Tab filter"
|
|
||||||
msgstr "↑↓ navegar · ↵ abrir · Tab filtrar"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1096
|
|
||||||
msgid "Pinned in Proton Drive"
|
|
||||||
msgstr "Fixado no Proton Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1111
|
|
||||||
msgid "Search your Drive"
|
|
||||||
msgstr "Pesquisar no seu Drive"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1116
|
|
||||||
msgid "Still indexing this computer — local results will fill in shortly."
|
|
||||||
msgstr ""
|
|
||||||
"Ainda indexando este computador — os resultados locais aparecerão em breve."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1118
|
|
||||||
msgid "Try a different search, or another filter."
|
|
||||||
msgstr "Tente uma pesquisa diferente ou outro filtro."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1120
|
|
||||||
msgid "Start typing to search Proton Drive and the files on this computer."
|
|
||||||
msgstr ""
|
|
||||||
"Comece a digitar para pesquisar no Proton Drive e nos arquivos deste "
|
|
||||||
"computador."
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1146
|
|
||||||
msgid "No pinned files"
|
|
||||||
msgstr "Nenhum arquivo fixado"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1147
|
|
||||||
#, rust-format
|
|
||||||
msgid "{n} pinned file"
|
|
||||||
msgid_plural "{n} pinned files"
|
|
||||||
msgstr[0] "{n} arquivo fixado"
|
|
||||||
msgstr[1] "{n} arquivos fixados"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1156
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer · indexing…"
|
|
||||||
msgstr "{drive} no Drive · {local} neste computador · indexando…"
|
|
||||||
|
|
||||||
#. Translators: result counts in the footer; {drive} and {local} are numbers.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1161
|
|
||||||
#, rust-format
|
|
||||||
msgid "{drive} in Drive · {local} on this computer"
|
|
||||||
msgstr "{drive} no Drive · {local} neste computador"
|
|
||||||
|
|
||||||
#. Translators: {name} is a file name.
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1294
|
|
||||||
#, rust-format
|
|
||||||
msgid "Opening {name}…"
|
|
||||||
msgstr "Abrindo {name}…"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1317
|
|
||||||
#, rust-format
|
|
||||||
msgid "Could not open: {message}"
|
|
||||||
msgstr "Não foi possível abrir: {message}"
|
|
||||||
|
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1321
|
|
||||||
msgid "Couldn't reach Proton Drive"
|
|
||||||
msgstr "Não foi possível acessar o Proton Drive"
|
|
||||||
|
|
||||||
#. Translators: the user's home folder, as the location of a result.
|
#. Translators: the user's home folder, as the location of a result.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1430
|
#: crates/pdfs-gui/src/prompt.rs:455
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Pasta pessoal"
|
msgstr "Pasta pessoal"
|
||||||
|
|
||||||
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
#. Translators: {path} is a folder path inside the home folder, folder names separated by " / ".
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1432
|
#: crates/pdfs-gui/src/prompt.rs:457
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "Home / {path}"
|
msgid "Home / {path}"
|
||||||
msgstr "Pasta pessoal / {path}"
|
msgstr "Pasta pessoal / {path}"
|
||||||
|
|
||||||
#. Translators: the age of a file modified under a minute ago.
|
#. Translators: the age of a file modified under a minute ago.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1452
|
#: crates/pdfs-gui/src/prompt.rs:477
|
||||||
msgctxt "age"
|
msgctxt "age"
|
||||||
msgid "now"
|
msgid "now"
|
||||||
msgstr "agora"
|
msgstr "agora"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of minutes.
|
#. Translators: a compact file age; {count} is a number of minutes.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1454
|
#: crates/pdfs-gui/src/prompt.rs:479
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}m"
|
msgid "{count}m"
|
||||||
msgstr "{count} min"
|
msgstr "{count} min"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of hours.
|
#. Translators: a compact file age; {count} is a number of hours.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1456
|
#: crates/pdfs-gui/src/prompt.rs:481
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}h"
|
msgid "{count}h"
|
||||||
msgstr "{count} h"
|
msgstr "{count} h"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of days.
|
#. Translators: a compact file age; {count} is a number of days.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1458
|
#: crates/pdfs-gui/src/prompt.rs:483
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}d"
|
msgid "{count}d"
|
||||||
msgstr "{count} d"
|
msgstr "{count} d"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of months.
|
#. Translators: a compact file age; {count} is a number of months.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1460
|
#: crates/pdfs-gui/src/prompt.rs:485
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}mo"
|
msgid "{count}mo"
|
||||||
msgstr "{count} m"
|
msgstr "{count} m"
|
||||||
|
|
||||||
#. Translators: a compact file age; {count} is a number of years.
|
#. Translators: a compact file age; {count} is a number of years.
|
||||||
#: crates/pdfs-gui/src/prompt.rs:1462
|
#: crates/pdfs-gui/src/prompt.rs:487
|
||||||
#, rust-format
|
#, rust-format
|
||||||
msgid "{count}y"
|
msgid "{count}y"
|
||||||
msgstr "{count} a"
|
msgstr "{count} a"
|
||||||
@@ -5944,9 +5987,6 @@ msgstr "não foi possível acessar o daemon do Proton Drive: {error}"
|
|||||||
#~ msgid "Use this computer's identity"
|
#~ msgid "Use this computer's identity"
|
||||||
#~ msgstr "Usar a identidade deste computador"
|
#~ msgstr "Usar a identidade deste computador"
|
||||||
|
|
||||||
#~ msgid "On this computer"
|
|
||||||
#~ msgstr "Neste computador"
|
|
||||||
|
|
||||||
#~ msgid ""
|
#~ msgid ""
|
||||||
#~ "Where Proton Drive lives on this machine. Synced folders keep a full copy "
|
#~ "Where Proton Drive lives on this machine. Synced folders keep a full copy "
|
||||||
#~ "on this disk; on-demand folders keep the files in Proton Drive only and "
|
#~ "on this disk; on-demand folders keep the files in Proton Drive only and "
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Publish the user documentation in docs/ to the GitHub wiki.
|
||||||
|
#
|
||||||
|
# The pages in docs/ are the source of truth; the wiki is a generated copy for
|
||||||
|
# people who look there first. Each run clones the wiki, replaces the pages
|
||||||
|
# listed below, rewrites links between them to wiki page names and every other
|
||||||
|
# repository link to an absolute GitHub URL, regenerates Home and _Sidebar, and
|
||||||
|
# commits. Nothing is pushed without --push.
|
||||||
|
#
|
||||||
|
# GitHub creates the wiki repository only when the first page is saved, so
|
||||||
|
# create any page once in the web UI before the first run.
|
||||||
|
#
|
||||||
|
# usage: scripts/publish-wiki.sh [--push] [WORKDIR]
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo_url="https://github.com/narrrl/proton-drive-linux"
|
||||||
|
wiki_remote="git@github.com:narrrl/proton-drive-linux.wiki.git"
|
||||||
|
|
||||||
|
push=0
|
||||||
|
if [ "${1:-}" = "--push" ]; then
|
||||||
|
push=1
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
workdir="${1:-$(mktemp -d)}"
|
||||||
|
wiki="$workdir/wiki"
|
||||||
|
|
||||||
|
if [ -d "$wiki/.git" ]; then
|
||||||
|
git -C "$wiki" pull --ff-only --quiet
|
||||||
|
else
|
||||||
|
git clone --quiet "$wiki_remote" "$wiki"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rev="$(git -C "$root" rev-parse --short HEAD)"
|
||||||
|
version="$(grep -m1 '^version' "$root/Cargo.toml" | cut -d'"' -f2)"
|
||||||
|
|
||||||
|
python3 - "$root" "$wiki" "$repo_url" "$version" <<'EOF'
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
root, wiki, repo_url, version = sys.argv[1:]
|
||||||
|
root = pathlib.Path(root)
|
||||||
|
wiki = pathlib.Path(wiki)
|
||||||
|
|
||||||
|
# docs/ file -> wiki page name, in sidebar order.
|
||||||
|
PAGES = {
|
||||||
|
"INSTALL.md": "Installation",
|
||||||
|
"USER_GUIDE.md": "User-Guide",
|
||||||
|
"CLI.md": "Command-Line-Reference",
|
||||||
|
"CONFIGURATION.md": "Configuration",
|
||||||
|
"TROUBLESHOOTING.md": "Troubleshooting",
|
||||||
|
"RECOVERY.md": "Recovery",
|
||||||
|
}
|
||||||
|
|
||||||
|
LINK = re.compile(r"(\]\()([^)\s]+)(\))")
|
||||||
|
|
||||||
|
|
||||||
|
def rewrite(target: str) -> str:
|
||||||
|
if re.match(r"^[a-z]+:|^#", target):
|
||||||
|
return target
|
||||||
|
path, _, anchor = target.partition("#")
|
||||||
|
anchor = f"#{anchor}" if anchor else ""
|
||||||
|
if path in PAGES:
|
||||||
|
return PAGES[path] + anchor
|
||||||
|
# Anything else is a file in the repository: link to it on GitHub.
|
||||||
|
resolved = (root / "docs" / path).resolve().relative_to(root)
|
||||||
|
kind = "tree" if (root / resolved).is_dir() else "blob"
|
||||||
|
return f"{repo_url}/{kind}/main/{resolved}{anchor}"
|
||||||
|
|
||||||
|
|
||||||
|
def convert(text: str) -> str:
|
||||||
|
return LINK.sub(lambda m: m.group(1) + rewrite(m.group(2)) + m.group(3), text)
|
||||||
|
|
||||||
|
|
||||||
|
note = (
|
||||||
|
"<!-- Generated from docs/ in the repository by scripts/publish-wiki.sh."
|
||||||
|
" Edit the source there; changes made here are overwritten. -->\n\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
for source, page in PAGES.items():
|
||||||
|
text = (root / "docs" / source).read_text()
|
||||||
|
(wiki / f"{page}.md").write_text(note + convert(text))
|
||||||
|
|
||||||
|
|
||||||
|
def title(source: str) -> str:
|
||||||
|
return (root / "docs" / source).read_text().splitlines()[0].lstrip("# ").strip()
|
||||||
|
|
||||||
|
|
||||||
|
home = [
|
||||||
|
note,
|
||||||
|
"# Proton Drive for Linux\n\n",
|
||||||
|
"An unofficial Proton Drive client for the Linux desktop: your files as a folder that ",
|
||||||
|
"downloads on demand, synced local folders, Photos, sharing, a GTK4 app with tray and search ",
|
||||||
|
"launcher, and a scriptable CLI.\n\n",
|
||||||
|
f"This wiki mirrors the documentation of version {version}. The source, releases and issue ",
|
||||||
|
f"tracker are in the [repository]({repo_url}).\n\n",
|
||||||
|
"## Pages\n\n",
|
||||||
|
]
|
||||||
|
home += [f"- [{title(source)}]({page})\n" for source, page in PAGES.items()]
|
||||||
|
home += [
|
||||||
|
"\n## More\n\n",
|
||||||
|
f"- [Changelog]({repo_url}/blob/main/docs/CHANGELOG.md)\n",
|
||||||
|
f"- [Architecture]({repo_url}/blob/main/docs/ARCHITECTURE.md)\n",
|
||||||
|
f"- [Contributing]({repo_url}/blob/main/CONTRIBUTING.md)\n",
|
||||||
|
f"- [Report a problem]({repo_url}/issues/new/choose)\n",
|
||||||
|
]
|
||||||
|
(wiki / "Home.md").write_text("".join(home))
|
||||||
|
|
||||||
|
sidebar = ["**[Home](Home)**\n\n"]
|
||||||
|
sidebar += [f"- [{title(source)}]({page})\n" for source, page in PAGES.items()]
|
||||||
|
sidebar += [
|
||||||
|
"\n---\n\n",
|
||||||
|
f"[Releases]({repo_url}/releases) · [Issues]({repo_url}/issues)\n",
|
||||||
|
]
|
||||||
|
(wiki / "_Sidebar.md").write_text("".join(sidebar))
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cd "$wiki"
|
||||||
|
git add -A
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "Wiki is already up to date."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
git commit --quiet -m "Update from docs at $rev"
|
||||||
|
git show --stat --format='%s' HEAD
|
||||||
|
|
||||||
|
if [ "$push" = 1 ]; then
|
||||||
|
git push --quiet
|
||||||
|
echo "Pushed to $wiki_remote."
|
||||||
|
else
|
||||||
|
echo "Committed in $wiki. Review it, then push from there or re-run with --push."
|
||||||
|
fi
|
||||||