i18n.rs
⎇
Raw
1//! Lightweight i18n: one central registry (in the spirit of `icons.rs`).
2//!
3//! `EN` is the canonical list of user-visible strings; it doubles as the key
4//! table. `DE` and `FR` translate the same keys; a key missing from a
5//! translation falls back to English.
6//!
7//! - `t(key)` resolves a key for the current locale. Non-reactive — for
8//! toasts, event handlers, and one-shot constructions (dialogs).
9//! - `tr(key)` is the same as a reactive closure, for `view!` children and
10//! attributes, so switching the language re-renders in place.
11//! - `error_text(code, fallback)` maps a server error *code* to a
12//! translation; unknown or absent codes fall back to the server message.
13//!
14//! Parameterized strings carry exactly one `{}` placeholder; callers do
15//! `format!(t(k::X), arg)`.
16//!
17//! Every table is sorted by key so lookups can binary-search.
18//!
19//! Locale precedence: the signed-in user's profile setting (persisted
20//! server-side), else the browser language. `init` + `set_locale` drive a
21//! global signal so every `tr` subscriber updates.
22
23use std::sync::OnceLock;
24
25use leptos::prelude::*;
26use wasm_bindgen::JsCast;
27
28/// A supported UI language.
29#[derive(Clone, Copy, PartialEq, Eq, Debug)]
30pub enum Locale {
31 En,
32 De,
33 Fr,
34}
35
36impl Locale {
37 pub const fn code(self) -> &'static str {
38 match self {
39 Locale::En => "en",
40 Locale::De => "de",
41 Locale::Fr => "fr",
42 }
43 }
44
45 /// Parse a BCP-47-ish tag ("de", "de-AT", "DE") into a supported locale.
46 pub fn from_code(code: &str) -> Option<Self> {
47 let lang = code.split(['-', '_']).next()?.to_ascii_lowercase();
48 match lang.as_str() {
49 "en" => Some(Locale::En),
50 "de" => Some(Locale::De),
51 "fr" => Some(Locale::Fr),
52 _ => None,
53 }
54 }
55
56 /// The browser's language, restricted to the supported locales.
57 pub fn from_browser() -> Option<Self> {
58 let lang = web_sys::window()?.navigator().language()?;
59 Self::from_code(&lang)
60 }
61}
62
63static READ: OnceLock<ReadSignal<Locale>> = OnceLock::new();
64static WRITE: OnceLock<WriteSignal<Locale>> = OnceLock::new();
65
66/// Called once during app setup, before the first UI render.
67pub fn init(locale: Locale) {
68 let (read, write) = signal(locale);
69 let _ = READ.set(read);
70 let _ = WRITE.set(write);
71 apply_doc_lang(locale);
72}
73
74pub fn set_locale(locale: Locale) {
75 if let Some(w) = WRITE.get() {
76 w.set(locale);
77 }
78 apply_doc_lang(locale);
79}
80
81pub fn current() -> Locale {
82 READ.get().map(|r| r.get()).unwrap_or(Locale::En)
83}
84
85/// Like [`current`] but without registering a reactive dependency — for
86/// code that reads the locale only to compare before writing (e.g. the
87/// profile effect in `app.rs`), so the read cannot retrigger itself.
88pub fn current_untracked() -> Locale {
89 READ.get().map(|r| r.get_untracked()).unwrap_or(Locale::En)
90}
91
92/// Keep `<html lang>` in sync for screen readers and the browser UI.
93fn apply_doc_lang(locale: Locale) {
94 if let Some(html) = web_sys::window()
95 .and_then(|w| w.document())
96 .and_then(|d| d.document_element())
97 .and_then(|e| e.dyn_into::<web_sys::HtmlElement>().ok())
98 {
99 let _ = html.set_attribute("lang", locale.code());
100 }
101}
102
103/// Resolve a key for the current locale (non-reactive).
104pub fn t(key: &str) -> &'static str {
105 text(key, current())
106}
107
108/// Resolve a key for the current locale as a reactive closure, for use as a
109/// `view!` child or attribute value.
110pub fn tr(key: &str) -> impl Fn() -> String + Clone {
111 move || t(key).to_string()
112}
113
114/// Like [`t`], but substitutes a single `{}` placeholder in the translation
115/// with `arg`.
116pub fn t_fmt(key: &str, arg: &str) -> String {
117 t(key).replace("{}", arg)
118}
119
120/// Like [`t_fmt`] for translations with two placeholders, filling the first
121/// `{}` with `a` and the second with `b`. Positional, so a locale that needs
122/// the other order has to reword rather than swap.
123pub fn t_fmt2(key: &str, a: &str, b: &str) -> String {
124 let s = t(key);
125 match s.split_once("{}") {
126 Some((head, tail)) => format!("{head}{a}{}", tail.replacen("{}", b, 1)),
127 None => s.to_string(),
128 }
129}
130
131/// Localize a server error: a known code maps to its translation, anything
132/// else (no code, unknown code) falls back to the server's English message.
133pub fn error_text(code: Option<&str>, fallback: &str) -> String {
134 let Some(code) = code.filter(|c| find(EN, c).is_some()) else {
135 return fallback.to_string();
136 };
137 // One-shot lookup for a toast message: no reactive context, so read the
138 // locale untracked.
139 text(code, current_untracked()).to_string()
140}
141
142fn text(key: &str, locale: Locale) -> &'static str {
143 // Keys are compile-time constants from `k`; a miss is a bug (the unit
144 // tests pin the tables against each other).
145 match locale {
146 Locale::En => find(EN, key).expect("unknown i18n key"),
147 Locale::De => find(DE, key)
148 .or_else(|| find(EN, key))
149 .expect("unknown i18n key"),
150 Locale::Fr => find(FR, key)
151 .or_else(|| find(EN, key))
152 .expect("unknown i18n key"),
153 }
154}
155
156/// The tables are sorted by key, so a lookup is a binary search. The
157/// `tables_are_sorted` test keeps that invariant true.
158fn find(table: &'static [(&'static str, &'static str)], key: &str) -> Option<&'static str> {
159 table
160 .binary_search_by(|(k, _)| (*k).cmp(key))
161 .ok()
162 .map(|i| table[i].1)
163}
164
165/// Declares the translation keys and the canonical English text in one
166/// place: one row per key, `CONST = "key" => "English"`.
167///
168/// The `k` constants and the `EN` table are generated from the same row, so
169/// a key cannot exist in one without the other. Rows are sorted by key,
170/// because [`find`] binary-searches (`tables_are_sorted` pins that).
171macro_rules! i18n_keys {
172 ($($name:ident = $key:literal => $en:expr),* $(,)?) => {
173 /// Translation keys. Use these, never the raw strings: a typo in a
174 /// key is a compile error, a typo in a string is a runtime panic.
175 pub mod k {
176 $(pub const $name: &str = $key;)*
177 }
178
179 /// Canonical English strings (key → text). The source of truth for
180 /// which keys exist; DE and FR translate the same list.
181 const EN: &[(&str, &str)] = &[$(($key, $en)),*];
182 };
183}
184
185i18n_keys! {
186 ACTIVE_CAN_SIGNIN = "active_can_signin" => "Active (can sign in)",
187 ADD_FOLDER = "add_folder" => "Add folder…",
188 ADD_HERE = "add_here" => "Add this folder",
189 ADD_PASSKEY = "add_passkey" => "Add passkey",
190 ADMINISTRATOR = "administrator" => "Administrator",
191 ALL_SHARES = "all_shares" => "All shares",
192 ALL_SHARES_HINT = "all_shares_hint" => "Every link on this server, grouped by the account that created it.",
193 ALLOW_EDITING = "allow_editing" => "Allow editing (read-write)",
194 ALLOW_RW_SHARES = "allow_rw_shares" => "Allow writable shares",
195 ALLOW_RW_SHARES_DESC = "allow_rw_shares_desc" => "Let users create read-write share links. Off by default.",
196 AND_MORE = "and_more" => "…and {} more",
197 CANCEL = "cancel" => "Cancel",
198 CANT_DELETE_SELF = "cant_delete_self" => "You cannot delete yourself",
199 CLOSE = "close" => "Close",
200 CLOSE_ESC = "close_esc" => "Close (Esc)",
201 COPIED = "copied" => "Copied",
202 COPY = "copy" => "Copy",
203 COPY_DAV_LINK = "copy_dav_link" => "Copy WebDAV link",
204 COPY_HERE = "copy_here" => "Copy here",
205 COPY_LINK = "copy_link" => "Copy link",
206 COPY_MANUAL = "copy_manual" => "Copy the link manually",
207 COPY_N_TO = "copy_n_to" => "Copy {} items to…",
208 COPY_TO = "copy_to" => "Copy “{}” to…",
209 CREATE = "create" => "Create",
210 CREATE_ADMIN = "create_admin" => "Create admin account",
211 CREATE_LINK = "create_link" => "Create link",
212 CREATE_USER = "create_user" => "Create user",
213 CREATING = "creating" => "Creating…",
214 CUSTOM = "custom" => "Custom…",
215 DEFAULT_ROOT_DESC = "default_root_desc" => "Open this folder on page load and on the home link.",
216 DEFAULT_ROOT_LABEL = "default_root_label" => "Default folder",
217 DEFAULT_ROOT_NONE = "default_root_none" => "Folder overview",
218 DELETE = "delete" => "Delete",
219 DELETE_ALL = "delete_all" => "Delete all",
220 DELETE_ALL_MSG = "delete_all_msg" => "Delete all {} share links? Anyone holding them loses access. This cannot be undone.",
221 DELETE_ALL_TITLE = "delete_all_title" => "Delete all shares?",
222 DELETE_ALL_USER_MSG = "delete_all_user_msg" => "Delete every share link of “{}”? Anyone holding them loses access. This cannot be undone.",
223 DELETE_FOLDER_MSG = "delete_folder_msg" => "Delete folder “{}” and everything inside it?\nThis cannot be undone.",
224 DELETE_MSG = "delete_msg" => "Delete “{}”?\nThis cannot be undone.",
225 DELETE_N_MSG = "delete_n_msg" => "Delete {} items?\nThis cannot be undone.",
226 DELETE_PARTIAL = "delete_partial" => "{} of {} shares could not be deleted",
227 DELETE_SHARE = "delete_share" => "Delete share",
228 DELETE_USER = "delete_user" => "Delete user",
229 DELETE_USER_CONFIRM = "delete_user_confirm" => "Delete user “{}”? This cannot be undone.",
230 DELETED = "deleted" => "Deleted",
231 DELETED_N_SHARES = "deleted_n_shares" => "Deleted {} shares",
232 DISCARD = "discard" => "Discard",
233 DISCARD_QUESTION = "discard_question" => "Discard your changes and close the editor?",
234 DISMISS = "dismiss" => "Dismiss",
235 DISMISS_ERROR = "dismiss_error" => "Dismiss error",
236 DONE = "done" => "Done",
237 DOWNLOAD = "download" => "Download",
238 DOWNLOAD_FORMATS_MSG = "download_formats_msg" => "Choose an archive format for the folder.",
239 DOWNLOAD_STARTED = "download_started" => "Download started",
240 DOWNLOAD_TITLE = "download_title" => "Download “{}”",
241 EDIT = "edit" => "Edit",
242 EDIT_USER_TITLE = "edit_user_title" => "Edit “{}”",
243 EMPTY_FOLDER = "empty_folder" => "This folder is empty.",
244 ERR_ADMIN_ONLY = "err_admin_only" => "admin only",
245 ERR_ALREADY_SET_UP = "err_already_set_up" => "server is already set up",
246 ERR_BAD_ACTION = "err_bad_action" => "expected action=content",
247 ERR_BAD_BODY = "err_bad_body" => "invalid request body",
248 ERR_BAD_EXPIRES_AT = "err_bad_expires_at" => "the expiry date is invalid",
249 ERR_BAD_FORMAT = "err_bad_format" => "format must be one of: zip, tar, tar.gz, tar.zst",
250 ERR_BAD_MULTIPART = "err_bad_multipart" => "expected multipart/form-data with a boundary",
251 ERR_BAD_PART_NAME = "err_bad_part_name" => "invalid part name",
252 ERR_BAD_POST = "err_bad_post" => "POST expects a multipart upload, a JSON mutation, or ?action=mkdir/create-file",
253 ERR_BAD_UPLOAD = "err_bad_upload" => "invalid upload data",
254 ERR_BAD_UPLOAD_PATH = "err_bad_upload_path" => "invalid file path in upload",
255 ERR_CHALLENGE_EXPIRED = "err_challenge_expired" => "That took too long, please try again.",
256 ERR_DST_REQUIRED = "err_dst_required" => "dst_root_id is required",
257 ERR_FILE_NAME_REQUIRED = "err_file_name_required" => "a file name is required",
258 ERR_FILES_EXIST = "err_files_exist" => "some files already exist",
259 ERR_FOLDER_EXISTS = "err_folder_exists" => "a folder with this name already exists",
260 ERR_FOLDER_NAME_REQUIRED = "err_folder_name_required" => "a folder name is required",
261 ERR_FS_CONFLICT = "err_fs_conflict" => "already exists",
262 ERR_FS_FORBIDDEN = "err_fs_forbidden" => "access denied",
263 ERR_FS_NOT_A_DIR = "err_fs_not_a_dir" => "not a folder",
264 ERR_FS_NOT_FOUND = "err_fs_not_found" => "folder not found",
265 ERR_FS_ROOT_MISSING = "err_fs_root_missing" => "the configured folder no longer exists",
266 ERR_INTERNAL = "err_internal" => "internal error",
267 ERR_INVALID_CREDENTIALS = "err_invalid_credentials" => "invalid name or password",
268 ERR_INVALID_DEFAULT_ROOT = "err_invalid_default_root" => "the default folder is not one of your folders",
269 ERR_INVALID_LANGUAGE = "err_invalid_language" => "invalid language tag",
270 ERR_LAST_ADMIN = "err_last_admin" => "cannot remove the last active admin",
271 ERR_LAST_ADMIN_DELETE = "err_last_admin_delete" => "cannot delete the last active admin",
272 ERR_LOCKED_OUT = "err_locked_out" => "That would leave the account with no way to sign in.",
273 ERR_MODE_NEEDS_PASSKEY = "err_mode_needs_passkey" => "Add a passkey before requiring both.",
274 ERR_MODE_NEEDS_PASSWORD = "err_mode_needs_password" => "Set a password before requiring both.",
275 ERR_NAME_LENGTH = "err_name_length" => "name must be 1–64 characters",
276 ERR_NEW_NAME_REQUIRED = "err_new_name_required" => "new_name is required",
277 ERR_NO_FILES_UPLOADED = "err_no_files_uploaded" => "no files were uploaded",
278 ERR_NO_SUCH_FOLDER = "err_no_such_folder" => "no such folder",
279 ERR_NOT_A_FILE = "err_not_a_file" => "not a file",
280 ERR_NOT_SIGNED_IN = "err_not_signed_in" => "not signed in",
281 ERR_OWN_ACCOUNT = "err_own_account" => "you cannot disable your own account",
282 ERR_OWN_ADMIN = "err_own_admin" => "you cannot remove your own admin rights",
283 ERR_OWN_DELETE = "err_own_delete" => "you cannot delete your own account",
284 ERR_PART_NO_NAME = "err_part_no_name" => "part without a name",
285 ERR_PASSKEY_DUPLICATE = "err_passkey_duplicate" => "That passkey is already registered.",
286 ERR_PASSKEY_FAILED = "err_passkey_failed" => "That passkey could not be used.",
287 ERR_PASSKEY_LAST_CREDENTIAL = "err_passkey_last_credential" => "Set a password before removing your last passkey.",
288 ERR_PASSKEY_LIMIT = "err_passkey_limit" => "This account already has as many passkeys as it may.",
289 ERR_PASSKEY_MALFORMED = "err_passkey_malformed" => "The browser sent unreadable data.",
290 ERR_PASSKEY_NOT_FOUND = "err_passkey_not_found" => "No such passkey.",
291 ERR_PASSKEY_UNAVAILABLE = "err_passkey_unavailable" => "Passkeys are not available here.",
292 ERR_PASSWORD_LAST_CREDENTIAL = "err_password_last_credential" => "Add a passkey before removing your password.",
293 ERR_PASSWORD_SHORT = "err_password_short" => "password must be at least 8 characters",
294 ERR_PATH_REQUIRED = "err_path_required" => "a path inside the folder is required",
295 ERR_READ_ONLY_FOLDER = "err_read_only_folder" => "read-only folder",
296 ERR_REQUIRED_BY_MODE = "err_required_by_mode" => "This account requires a password and a passkey.",
297 ERR_ROOT_FORBIDDEN = "err_root_forbidden" => "root not accessible",
298 ERR_RW_RO_FOLDER = "err_rw_ro_folder" => "this folder is read-only for you, so it cannot be shared writably",
299 ERR_RW_SHARES_DISABLED = "err_rw_shares_disabled" => "writable shares are disabled",
300 ERR_SAVE_FAILED = "err_save_failed" => "could not save the file",
301 ERR_SEARCH_BAD_SCOPE = "err_search_bad_scope" => "scope must be name, content or both",
302 ERR_SEARCH_FORBIDDEN = "err_search_forbidden" => "search requires a signed-in session",
303 ERR_SESSION_EXPIRED = "err_session_expired" => "session expired, please sign in again",
304 ERR_SHARE_EXPIRED = "err_share_expired" => "this share has expired",
305 ERR_SHARE_LOCKED = "err_share_locked" => "This share is password protected.",
306 ERR_SHARE_NO_PASSWORD = "err_share_no_password" => "This share has no password.",
307 ERR_SHARE_NOT_FOUND = "err_share_not_found" => "share not found",
308 ERR_SHARE_TOKEN_FORBIDDEN = "err_share_token_forbidden" => "a share token cannot be used here; sign in instead",
309 ERR_SHARE_WRONG_PASSWORD = "err_share_wrong_password" => "Wrong password.",
310 ERR_TOO_LARGE_PREVIEW = "err_too_large_preview" => "file too large to preview",
311 ERR_TOO_LARGE_SAVE = "err_too_large_save" => "file too large to save",
312 ERR_USER_EXISTS = "err_user_exists" => "a user with that name already exists",
313 ERR_USER_NOT_FOUND = "err_user_not_found" => "user not found",
314 EXCLUDE_DUP_ERR = "exclude_dup_err" => "That folder is already excluded.",
315 EXCLUDE_FOLDER = "exclude_folder" => "Exclude folder…",
316 EXCLUDE_HERE = "exclude_here" => "Exclude this folder",
317 EXCLUDE_ROOT_ERR = "exclude_root_err" => "The whole root cannot be excluded; that would turn search off.",
318 EXPIRES = "expires" => "Expires",
319 EXPIRES_AT = "expires_at" => "Expires {}",
320 EXPIRY_1D = "expiry_1d" => "1 day",
321 EXPIRY_1H = "expiry_1h" => "1 hour",
322 EXPIRY_1MO = "expiry_1mo" => "1 month",
323 EXPIRY_1W = "expiry_1w" => "1 week",
324 FILE = "file" => "File",
325 FILE_CHANGED = "file_changed" => "File changed on disk",
326 FILE_CREATED = "file_created" => "File created",
327 FILE_INFO = "file_info" => "File info",
328 FILE_NAME = "file_name" => "File name",
329 FILES = "files" => "Files",
330 FMT_TAR = "fmt_tar" => "Tar",
331 FMT_TARGZ = "fmt_targz" => "Tar + gzip",
332 FMT_TARZST = "fmt_tarzst" => "Tar + zstd",
333 FMT_ZIP = "fmt_zip" => "Zip",
334 FOLDER = "folder" => "Folder",
335 FOLDER_CREATED = "folder_created" => "Folder created",
336 FOLDER_GONE_MSG = "folder_gone_msg" => "This folder is no longer accessible. Choose another one from the sidebar.",
337 FOLDER_GONE_TITLE = "folder_gone_title" => "Folder not available",
338 FOLDER_NAME = "folder_name" => "Folder name",
339 FOLDERS = "folders" => "Folders",
340 FOLDERS_PATHS_HINT = "folders_paths_hint" => "Paths are relative to the server root. “.” is the whole root.",
341 GRID_VIEW = "grid_view" => "Grid view",
342 INFO = "info" => "Info",
343 KEEP_EDITING = "keep_editing" => "Keep editing",
344 LANG_AUTO = "lang_auto" => "Automatic",
345 LANG_DE = "lang_de" => "German",
346 LANG_EN = "lang_en" => "English",
347 LANG_FR = "lang_fr" => "French",
348 LANGUAGE = "language" => "Language",
349 LANGUAGE_DESC = "language_desc" => "The interface language. Automatic uses your browser's language.",
350 LINK_COPIED = "link_copied" => "Link copied to clipboard",
351 LIST_TRUNCATED = "list_truncated" => "Only the first {} entries are shown.",
352 LIST_VIEW = "list_view" => "List view",
353 LOAD_LATEST = "load_latest" => "Load latest",
354 LOADING = "loading" => "Loading…",
355 LOADING_SHARE = "loading_share" => "Loading share…",
356 LOCATION = "location" => "Location",
357 LOG_OUT = "log_out" => "Log out",
358 LOGIN_ERROR = "login_error" => "Please enter your name and password.",
359 LOGIN_SUBTITLE = "login_subtitle" => "Sign in to continue.",
360 MANAGE = "manage" => "Manage",
361 MENU = "menu" => "Menu",
362 META_CREATED = "meta_created" => "created {}",
363 META_EXPIRES = "meta_expires" => "expires {}",
364 MODE_RO = "mode_ro" => "read-only",
365 MODE_RW = "mode_rw" => "read-write",
366 MODIFIED = "modified" => "Modified",
367 MODIFIED_AFTER = "modified_after" => "This file was modified after you opened it. “Load latest” discards your unsaved changes; “Overwrite” keeps your version.",
368 MORE_ACTIONS = "more_actions" => "More actions",
369 MOVE = "move" => "Move",
370 MOVE_HERE = "move_here" => "Move here",
371 MOVE_N_TO = "move_n_to" => "Move {} items to…",
372 MOVE_TO = "move_to" => "Move “{}” to…",
373 MOVED = "moved" => "Moved",
374 NAME = "name" => "Name",
375 NAME_REQUIRED = "name_required" => "A name is required.",
376 NAME_TAKEN = "name_taken" => "Name already in use",
377 NEED_FOLDER = "need_folder" => "Give the user at least one folder.",
378 NEVER = "never" => "Never",
379 NEVER_EXPIRES = "never_expires" => "Never expires",
380 NEW_FILE = "new_file" => "New file",
381 NEW_FOLDER = "new_folder" => "New folder",
382 NEW_NAME = "new_name" => "New name",
383 NEW_PASSWORD = "new_password" => "New password",
384 NEW_PASSWORD_HINT = "new_password_hint" => "Leave empty to keep your current password.",
385 NEW_PASSWORD_KEEP = "new_password_keep" => "New password (leave blank to keep)",
386 NEW_USER = "new_user" => "New user",
387 NO_FOLDERS_MSG = "no_folders_msg" => "An administrator hasn't assigned you any folders yet.",
388 NO_FOLDERS_TITLE = "no_folders_title" => "No folders available",
389 NO_PASSKEYS = "no_passkeys" => "No passkeys yet.",
390 NO_PASSWORD_SET = "no_password_set" => "This account signs in with passkeys only.",
391 NO_PREVIEW = "no_preview" => "No preview available",
392 NO_PREVIEW_MSG = "no_preview_msg" => "The app can’t display “{}” here.",
393 NO_SHARES = "no_shares" => "No shares yet",
394 NO_SUBFOLDERS = "no_subfolders" => "No subfolders",
395 NO_USERS = "no_users" => "No users.",
396 OPEN = "open" => "Open",
397 OPEN_IN_NEW_TAB = "open_in_new_tab" => "Open in new tab",
398 OPEN_MENU = "open_menu" => "Open menu",
399 OR_LABEL = "or_label" => "or",
400 OVERWRITE = "overwrite" => "Overwrite",
401 OVERWRITE_QUESTION = "overwrite_question" => "The following files already exist. Overwrite them?",
402 PASSKEY_ADDED = "passkey_added" => "Passkey added",
403 PASSKEY_ADDED_ON = "passkey_added_on" => "Added {}",
404 PASSKEY_LABEL = "passkey_label" => "Name for this passkey",
405 PASSKEY_LAST_USED = "passkey_last_used" => "Last used {}",
406 PASSKEY_NEEDS_NAME = "passkey_needs_name" => "Needs your user name",
407 PASSKEY_NEEDS_NAME_HINT = "passkey_needs_name_hint" => "This passkey is not stored on the authenticator itself, so the server has to look it up. Type your user name on the sign-in page before using it.",
408 PASSKEY_NEVER_USED = "passkey_never_used" => "Never used",
409 PASSKEY_NOT_USED = "passkey_not_used" => "No passkey was used.",
410 PASSKEY_REMOVED = "passkey_removed" => "Passkey removed",
411 PASSKEY_UNSUPPORTED = "passkey_unsupported" => "This browser cannot use passkeys.",
412 PASSKEYS = "passkeys" => "Passkeys",
413 PASSKEYS_HINT = "passkeys_hint" => "A passkey signs you in with your fingerprint, your face or a security key.",
414 PASSWORD = "password" => "Password",
415 PASSWORD_REMOVED = "password_removed" => "Password removed",
416 PASSWORD_RESETS_SIGNIN = "password_resets_signin" => "Setting a password also deletes this account's passkeys and drops any two-factor requirement.",
417 PROFILE = "profile" => "Profile",
418 PROFILE_SAVE_ERR = "profile_save_err" => "Could not save profile: {}",
419 PROFILE_SAVED = "profile_saved" => "Profile saved",
420 PW_SHORT = "pw_short" => "Password must be at least 8 characters.",
421 READ_ONLY_BADGE = "read_only_badge" => "Read-only",
422 READ_ONLY_HINT = "read_only_hint" => "Read-only folder",
423 REFRESH = "refresh" => "Refresh",
424 REFRESH_FOLDERS = "refresh_folders" => "Refresh your folders",
425 REMOVE_FOLDER = "remove_folder" => "Remove folder",
426 REMOVE_PASSWORD = "remove_password" => "Remove password",
427 REMOVE_PASSWORD_DESC = "remove_password_desc" => "Sign in with passkeys only. Needs at least one passkey.",
428 RENAME = "rename" => "Rename",
429 RENAME_TITLE = "rename_title" => "Rename “{}”",
430 RENAMED = "renamed" => "Renamed",
431 REPEAT_PASSWORD = "repeat_password" => "Repeat password",
432 RETRY = "retry" => "Retry",
433 ROOT_DUP_ERR = "root_dup_err" => "That folder is already in the list.",
434 RW_DISABLED = "rw_disabled" => "Writable shares are disabled by the server.",
435 RW_RO_FOLDER = "rw_ro_folder" => "This folder is read-only, so the share is read-only too.",
436 SAVE = "save" => "Save",
437 SAVE_PROFILE = "save_profile" => "Save profile",
438 SAVE_SETTINGS = "save_settings" => "Save settings",
439 SAVED = "saved" => "Saved",
440 SAVING = "saving" => "Saving…",
441 SEARCH = "search" => "Search",
442 SEARCH_BOTH = "search_both" => "Both",
443 SEARCH_COLLAPSE_ALL = "search_collapse_all" => "Collapse all",
444 SEARCH_CONTENT = "search_content" => "Content",
445 SEARCH_EXCLUDES = "search_excludes" => "Excluded from search",
446 SEARCH_EXCLUDES_EMPTY = "search_excludes_empty" => "No folders are excluded.",
447 SEARCH_EXPAND_ALL = "search_expand_all" => "Expand all",
448 SEARCH_FAILED = "search_failed" => "The search failed.",
449 SEARCH_GOTO = "search_goto" => "Show in folder",
450 SEARCH_HERE = "search_here" => "Search here",
451 SEARCH_IN = "search_in" => "Search in…",
452 SEARCH_IN_FOLDER = "search_in_folder" => "Search in this folder",
453 SEARCH_IN_N_FILES = "search_in_n_files" => "in {} files",
454 SEARCH_N_MATCHES = "search_n_matches" => "{} matches",
455 SEARCH_N_RESULTS = "search_n_results" => "{} results",
456 SEARCH_NAME = "search_name" => "Name",
457 SEARCH_NO_RESULTS = "search_no_results" => "no results",
458 SEARCH_PLACEHOLDER = "search_placeholder" => "Search files and their contents",
459 SEARCH_RUN = "search_run" => "Search",
460 SEARCH_SCANNED = "search_scanned" => "scanned {} files",
461 SEARCH_SHOW_MORE = "search_show_more" => "Show {} more",
462 SEARCH_SHOWN_OF = "search_shown_of" => "Showing {} of {}",
463 SEARCH_SKIPPED = "search_skipped" => "{} skipped (over 10 MiB)",
464 SEARCH_STOP = "search_stop" => "Stop",
465 SEARCH_STOPPED = "search_stopped" => "stopped · {} results",
466 SEARCH_TOOK = "search_took" => "{} ms",
467 SEARCHING = "searching" => "searching…",
468 SECOND_FACTOR_PASSKEY = "second_factor_passkey" => "Now confirm with your passkey.",
469 SECOND_FACTOR_PASSWORD = "second_factor_password" => "Enter the password for {}.",
470 SECURITY = "security" => "Security",
471 SELECT_FOLDER_MSG = "select_folder_msg" => "Choose a folder from the sidebar to start browsing.",
472 SELECT_FOLDER_TITLE = "select_folder_title" => "Select a folder",
473 SERVER = "server" => "Server",
474 SERVER_ROOT = "server_root" => "Server root",
475 SERVER_UNREACHABLE = "server_unreachable" => "The server is not reachable.",
476 SETTINGS = "settings" => "Settings",
477 SETTINGS_SAVE_ERR = "settings_save_err" => "Could not save settings: {}",
478 SETTINGS_SAVED = "settings_saved" => "Settings saved",
479 SETUP_INTRO = "setup_intro" => "First boot: create the admin account. This user can manage other users and server settings.",
480 SETUP_NAME_ERR = "setup_name_err" => "Please choose a name (1–64 characters).",
481 SETUP_PW_MISMATCH = "setup_pw_mismatch" => "Passwords don't match.",
482 SHARE = "share" => "Share",
483 SHARE_CREATE_ERR = "share_create_err" => "Could not create share: {}",
484 SHARE_DELETE_ERR = "share_delete_err" => "Could not delete share",
485 SHARE_DELETED = "share_deleted" => "Share deleted",
486 SHARE_DIALOG_HINT = "share_dialog_hint" => "Create a link for this item. Writable links let viewers upload and edit.",
487 SHARE_EXPIRED = "share_expired" => "Share expired",
488 SHARE_EXPIRED_MSG = "share_expired_msg" => "This share link is no longer active.",
489 SHARE_LINK = "share_link" => "Share link",
490 SHARE_LOCKED_TITLE = "share_locked_title" => "Password required",
491 SHARE_NOT_FOUND = "share_not_found" => "Share not found",
492 SHARE_NOT_FOUND_MSG = "share_not_found_msg" => "This link is invalid or the share was removed.",
493 SHARE_OWNER_DISABLED = "share_owner_disabled" => "deactivated account",
494 SHARE_PASSWORD_HINT = "share_password_hint" => "Optional. Visitors must enter it before the share opens, at least 8 characters.",
495 SHARE_PERM_RO = "share_perm_ro" => "Anyone with this link can read “{}”.",
496 SHARE_PERM_RW = "share_perm_rw" => "Anyone with this link can read and write “{}”.",
497 SHARE_PROTECTED = "share_protected" => "Password protected",
498 SHARE_TITLE = "share_title" => "Share {}",
499 SHARES = "shares" => "Shares",
500 SHARES_DELETE_ERR = "shares_delete_err" => "Could not delete shares",
501 SHARES_EMPTY_HINT = "shares_empty_hint" => "Right-click a file or folder and choose “Share” to create a link.",
502 SHARES_VIEW_HINT = "shares_view_hint" => "Links you created. Right-click a file or folder and choose “Share” to add one.",
503 SIGN_IN = "sign_in" => "Sign in",
504 SIGNIN_BOTH = "signin_both" => "Password and passkey",
505 SIGNIN_BOTH_DESC = "signin_both_desc" => "Both are required. WebDAV mounts stop working, because they can only send a password.",
506 SIGNIN_EITHER = "signin_either" => "Password or passkey",
507 SIGNIN_EITHER_DESC = "signin_either_desc" => "Either one on its own signs you in.",
508 SIGNIN_REQUIREMENT = "signin_requirement" => "Sign-in requirement",
509 SINGLE_CLICK_DESC = "single_click_desc" => "When on, a single click opens an entry and Ctrl+click selects it. When off, a single click selects and a double click opens.",
510 SINGLE_CLICK_LABEL = "single_click_label" => "Use single click to open",
511 SIZE = "size" => "Size",
512 SOME_FILES_EXIST = "some_files_exist" => "Some files already exist",
513 SORT_ASC = "sort_asc" => "Ascending",
514 SORT_BY = "sort_by" => "Sort by",
515 SORT_DESC = "sort_desc" => "Descending",
516 THEME_AUTO = "theme_auto" => "Auto",
517 THEME_DARK = "theme_dark" => "Dark",
518 THEME_LIGHT = "theme_light" => "Light",
519 THEME_TITLE = "theme_title" => "Theme: {} (click to switch)",
520 THUMBNAILS_DESC = "thumbnails_desc" => "Show a small preview instead of an icon for images and videos in grid view.",
521 THUMBNAILS_LABEL = "thumbnails_label" => "Show thumbnails",
522 TYPE = "type" => "Type",
523 UNLOCK = "unlock" => "Open share",
524 UNSAVED_CHANGES = "unsaved_changes" => "Unsaved changes",
525 UNTIL = "until" => "Until",
526 UPLOAD_ABORT = "upload_abort" => "Abort",
527 UPLOAD_ABORTED = "upload_aborted" => "Aborted",
528 UPLOAD_ACTIVE_N = "upload_active_n" => "{} active",
529 UPLOAD_APPLY_ALL = "upload_apply_all" => "Apply to files that appear during the upload",
530 UPLOAD_CHECK_FAILED = "upload_check_failed" => "Could not check for existing files: {}",
531 UPLOAD_CLEAR = "upload_clear" => "Remove from list",
532 UPLOAD_CLEAR_FINISHED = "upload_clear_finished" => "Clear finished",
533 UPLOAD_COLLAPSE = "upload_collapse" => "Collapse",
534 UPLOAD_COMPLETE = "upload_complete" => "Upload complete",
535 UPLOAD_CONFLICT_QUESTION = "upload_conflict_question" => "The following files already exist. Overwrite or skip them?",
536 UPLOAD_DROP_HINT = "upload_drop_hint" => "Drop to upload here",
537 UPLOAD_ERRORS_N = "upload_errors_n" => "{} failed",
538 UPLOAD_EXISTS_ASK = "upload_exists_ask" => "“{}” appeared on the server during the upload.",
539 UPLOAD_EXPAND = "upload_expand" => "Expand",
540 UPLOAD_FAILED = "upload_failed" => "Failed",
541 UPLOAD_FILES = "upload_files" => "Upload files",
542 UPLOAD_FOLDER = "upload_folder" => "Upload folder",
543 UPLOAD_N_FILES = "upload_n_files" => "{} files",
544 UPLOAD_PAUSE = "upload_pause" => "Pause",
545 UPLOAD_PAUSED = "upload_paused" => "Paused",
546 UPLOAD_PREPARING = "upload_preparing" => "Preparing…",
547 UPLOAD_RESUME = "upload_resume" => "Resume",
548 UPLOAD_SHOW_GRAPH = "upload_show_graph" => "Details and speed graph",
549 UPLOAD_SKIP = "upload_skip" => "Skip",
550 UPLOAD_SKIPPED_N = "upload_skipped_n" => "{} skipped",
551 UPLOAD_SPEED = "upload_speed" => "{}/s",
552 UPLOAD_WAITING = "upload_waiting" => "Waiting for your answer",
553 UPLOADS = "uploads" => "Uploads",
554 UPLOADS_EMPTY = "uploads_empty" => "No uploads yet",
555 USE_PASSKEY = "use_passkey" => "Use a passkey",
556 USE_PASSKEY_HINT = "use_passkey_hint" => "Leave the name empty unless your passkey needs it.",
557 USER_CREATED = "user_created" => "User created",
558 USER_DELETE_ERR = "user_delete_err" => "Could not delete user",
559 USER_SAVE_ERR = "user_save_err" => "Could not save user: {}",
560 USER_UPDATED = "user_updated" => "User updated",
561 USERS = "users" => "Users",
562 YOUR_SHARES = "your_shares" => "Your shares",
563}
564
565/// German translations.
566const DE: &[(&str, &str)] = &[
567 ("active_can_signin", "Aktiv (kann sich anmelden)"),
568 ("add_folder", "Ordner hinzufügen…"),
569 ("add_here", "Diesen Ordner hinzufügen"),
570 ("add_passkey", "Passkey hinzufügen"),
571 ("administrator", "Administrator"),
572 ("all_shares", "Alle Freigaben"),
573 (
574 "all_shares_hint",
575 "Jeder Link auf diesem Server, gruppiert nach dem Konto, das ihn erstellt hat.",
576 ),
577 ("allow_editing", "Bearbeitung erlauben (lesen/schreiben)"),
578 ("allow_rw_shares", "Schreibbare Freigaben erlauben"),
579 (
580 "allow_rw_shares_desc",
581 "Benutzern erlauben, schreibbare Freigabelinks zu erstellen. Standardmäßig aus.",
582 ),
583 ("and_more", "…und {} weitere"),
584 ("cancel", "Abbrechen"),
585 ("cant_delete_self", "Sie können sich nicht selbst löschen"),
586 ("close", "Schließen"),
587 ("close_esc", "Schließen (Esc)"),
588 ("copied", "Kopiert"),
589 ("copy", "Kopieren"),
590 ("copy_dav_link", "WebDAV-Link kopieren"),
591 ("copy_here", "Hierher kopieren"),
592 ("copy_link", "Link kopieren"),
593 ("copy_manual", "Link manuell kopieren"),
594 ("copy_n_to", "{} Elemente kopieren nach…"),
595 ("copy_to", "„{}“ kopieren nach…"),
596 ("create", "Erstellen"),
597 ("create_admin", "Administrator-Konto erstellen"),
598 ("create_link", "Link erstellen"),
599 ("create_user", "Benutzer erstellen"),
600 ("creating", "Wird erstellt…"),
601 ("custom", "Benutzerdefiniert…"),
602 (
603 "default_root_desc",
604 "Diesen Ordner beim Laden der Seite und über den Home-Link öffnen.",
605 ),
606 ("default_root_label", "Standardordner"),
607 ("default_root_none", "Ordnerübersicht"),
608 ("delete", "Löschen"),
609 ("delete_all", "Alle löschen"),
610 (
611 "delete_all_msg",
612 "Alle {} Freigabelinks löschen? Jeder, der sie besitzt, verliert den Zugriff. Das kann nicht rückgängig gemacht werden.",
613 ),
614 ("delete_all_title", "Alle Freigaben löschen?"),
615 (
616 "delete_all_user_msg",
617 "Alle Freigabelinks von „{}“ löschen? Jeder, der sie besitzt, verliert den Zugriff. Das kann nicht rückgängig gemacht werden.",
618 ),
619 (
620 "delete_folder_msg",
621 "Ordner „{}“ mit allem Inhalt löschen?\nDas kann nicht rückgängig gemacht werden.",
622 ),
623 (
624 "delete_msg",
625 "„{}“ löschen?\nDas kann nicht rückgängig gemacht werden.",
626 ),
627 (
628 "delete_n_msg",
629 "{} Elemente löschen?\nDas kann nicht rückgängig gemacht werden.",
630 ),
631 (
632 "delete_partial",
633 "{} von {} Freigaben konnten nicht gelöscht werden",
634 ),
635 ("delete_share", "Freigabe löschen"),
636 ("delete_user", "Benutzer löschen"),
637 (
638 "delete_user_confirm",
639 "Benutzer „{}“ löschen? Das kann nicht rückgängig gemacht werden.",
640 ),
641 ("deleted", "Gelöscht"),
642 ("deleted_n_shares", "{} Freigabe(n) gelöscht"),
643 ("discard", "Verwerfen"),
644 (
645 "discard_question",
646 "Änderungen verwerfen und den Editor schließen?",
647 ),
648 ("dismiss", "Ausblenden"),
649 ("dismiss_error", "Fehler ausblenden"),
650 ("done", "Fertig"),
651 ("download", "Herunterladen"),
652 (
653 "download_formats_msg",
654 "Archivformat für den Ordner wählen.",
655 ),
656 ("download_started", "Download gestartet"),
657 ("download_title", "„{}“ herunterladen"),
658 ("edit", "Bearbeiten"),
659 ("edit_user_title", "„{}“ bearbeiten"),
660 ("empty_folder", "Dieser Ordner ist leer."),
661 (k::ERR_ADMIN_ONLY, "nur für Administratoren"),
662 (k::ERR_ALREADY_SET_UP, "Server ist bereits eingerichtet"),
663 (k::ERR_BAD_ACTION, "action=content erwartet"),
664 (k::ERR_BAD_BODY, "ungültiger Anfrageinhalt"),
665 (k::ERR_BAD_EXPIRES_AT, "das Ablaufdatum ist ungültig"),
666 (
667 k::ERR_BAD_FORMAT,
668 "das Format muss eines der folgenden sein: zip, tar, tar.gz, tar.zst",
669 ),
670 (
671 k::ERR_BAD_MULTIPART,
672 "multipart/form-data mit Boundary erwartet",
673 ),
674 (k::ERR_BAD_PART_NAME, "ungültiger Part-Name"),
675 (
676 k::ERR_BAD_POST,
677 "POST erwartet einen Multipart-Upload, eine JSON-Mutation oder ?action=mkdir/create-file",
678 ),
679 (k::ERR_BAD_UPLOAD, "ungültige Upload-Daten"),
680 (k::ERR_BAD_UPLOAD_PATH, "ungültiger Dateipfad im Upload"),
681 (
682 k::ERR_CHALLENGE_EXPIRED,
683 "Das hat zu lange gedauert. Bitte erneut versuchen.",
684 ),
685 (k::ERR_DST_REQUIRED, "dst_root_id ist erforderlich"),
686 (k::ERR_FILE_NAME_REQUIRED, "ein Dateiname ist erforderlich"),
687 (k::ERR_FILES_EXIST, "einige Dateien existieren bereits"),
688 (
689 k::ERR_FOLDER_EXISTS,
690 "ein Ordner mit diesem Namen existiert bereits",
691 ),
692 (
693 k::ERR_FOLDER_NAME_REQUIRED,
694 "ein Ordnername ist erforderlich",
695 ),
696 (k::ERR_FS_CONFLICT, "existiert bereits"),
697 (k::ERR_FS_FORBIDDEN, "Zugriff verweigert"),
698 (k::ERR_FS_NOT_A_DIR, "kein Ordner"),
699 (k::ERR_FS_NOT_FOUND, "Ordner nicht gefunden"),
700 (
701 k::ERR_FS_ROOT_MISSING,
702 "der konfigurierte Ordner existiert nicht mehr",
703 ),
704 (k::ERR_INTERNAL, "interner Fehler"),
705 (k::ERR_INVALID_CREDENTIALS, "Name oder Passwort ungültig"),
706 (
707 k::ERR_INVALID_DEFAULT_ROOT,
708 "Der Standardordner gehört nicht zu Ihren Ordnern",
709 ),
710 (k::ERR_INVALID_LANGUAGE, "ungültiges Sprachkürzel"),
711 (
712 k::ERR_LAST_ADMIN,
713 "der letzte aktive Administrator kann nicht entfernt werden",
714 ),
715 (
716 k::ERR_LAST_ADMIN_DELETE,
717 "der letzte aktive Administrator kann nicht gelöscht werden",
718 ),
719 (
720 k::ERR_LOCKED_OUT,
721 "Dann hätte das Konto keine Anmeldemöglichkeit mehr.",
722 ),
723 (
724 k::ERR_MODE_NEEDS_PASSKEY,
725 "Fügen Sie einen Passkey hinzu, bevor Sie beides verlangen.",
726 ),
727 (
728 k::ERR_MODE_NEEDS_PASSWORD,
729 "Setzen Sie ein Passwort, bevor Sie beides verlangen.",
730 ),
731 (k::ERR_NAME_LENGTH, "der Name muss 1–64 Zeichen haben"),
732 (k::ERR_NEW_NAME_REQUIRED, "new_name ist erforderlich"),
733 (k::ERR_NO_FILES_UPLOADED, "keine Dateien hochgeladen"),
734 (k::ERR_NO_SUCH_FOLDER, "dieser Ordner existiert nicht"),
735 (k::ERR_NOT_A_FILE, "keine Datei"),
736 (k::ERR_NOT_SIGNED_IN, "nicht angemeldet"),
737 (
738 k::ERR_OWN_ACCOUNT,
739 "Sie können Ihr eigenes Konto nicht deaktivieren",
740 ),
741 (
742 k::ERR_OWN_ADMIN,
743 "Sie können Ihre eigenen Administratorrechte nicht entfernen",
744 ),
745 (
746 k::ERR_OWN_DELETE,
747 "Sie können Ihr eigenes Konto nicht löschen",
748 ),
749 (k::ERR_PART_NO_NAME, "Part ohne Namen"),
750 (
751 k::ERR_PASSKEY_DUPLICATE,
752 "Dieser Passkey ist bereits registriert.",
753 ),
754 (
755 k::ERR_PASSKEY_FAILED,
756 "Dieser Passkey konnte nicht verwendet werden.",
757 ),
758 (
759 k::ERR_PASSKEY_LAST_CREDENTIAL,
760 "Setzen Sie ein Passwort, bevor Sie den letzten Passkey entfernen.",
761 ),
762 (
763 k::ERR_PASSKEY_LIMIT,
764 "Dieses Konto hat bereits so viele Passkeys wie erlaubt.",
765 ),
766 (
767 k::ERR_PASSKEY_MALFORMED,
768 "Der Browser hat unlesbare Daten gesendet.",
769 ),
770 (k::ERR_PASSKEY_NOT_FOUND, "Passkey nicht gefunden."),
771 (
772 k::ERR_PASSKEY_UNAVAILABLE,
773 "Passkeys sind hier nicht verfügbar.",
774 ),
775 (
776 k::ERR_PASSWORD_LAST_CREDENTIAL,
777 "Fügen Sie einen Passkey hinzu, bevor Sie das Passwort entfernen.",
778 ),
779 (
780 k::ERR_PASSWORD_SHORT,
781 "das Passwort muss mindestens 8 Zeichen lang sein",
782 ),
783 (
784 k::ERR_PATH_REQUIRED,
785 "ein Pfad innerhalb des Ordners ist erforderlich",
786 ),
787 (k::ERR_READ_ONLY_FOLDER, "schreibgeschützter Ordner"),
788 (
789 k::ERR_REQUIRED_BY_MODE,
790 "Dieses Konto verlangt Passwort und Passkey.",
791 ),
792 (k::ERR_ROOT_FORBIDDEN, "kein Zugriff auf diesen Ordner"),
793 (
794 k::ERR_RW_RO_FOLDER,
795 "dieser Ordner ist für Sie schreibgeschützt und kann nicht schreibbar freigegeben werden",
796 ),
797 (
798 k::ERR_RW_SHARES_DISABLED,
799 "schreibbare Freigaben sind deaktiviert",
800 ),
801 (
802 k::ERR_SAVE_FAILED,
803 "die Datei konnte nicht gespeichert werden",
804 ),
805 (
806 k::ERR_SEARCH_BAD_SCOPE,
807 "Bereich muss name, content oder both sein",
808 ),
809 (
810 k::ERR_SEARCH_FORBIDDEN,
811 "Suche erfordert eine angemeldete Sitzung",
812 ),
813 (
814 k::ERR_SESSION_EXPIRED,
815 "Sitzung abgelaufen, bitte erneut anmelden",
816 ),
817 (k::ERR_SHARE_EXPIRED, "diese Freigabe ist abgelaufen"),
818 (k::ERR_SHARE_LOCKED, "Diese Freigabe ist passwortgeschützt."),
819 (
820 k::ERR_SHARE_NO_PASSWORD,
821 "Diese Freigabe hat kein Passwort.",
822 ),
823 (k::ERR_SHARE_NOT_FOUND, "Freigabe nicht gefunden"),
824 (
825 k::ERR_SHARE_TOKEN_FORBIDDEN,
826 "ein Freigabetoken kann hier nicht verwendet werden; bitte anmelden",
827 ),
828 (k::ERR_SHARE_WRONG_PASSWORD, "Falsches Passwort."),
829 (k::ERR_TOO_LARGE_PREVIEW, "Datei zu groß für eine Vorschau"),
830 (k::ERR_TOO_LARGE_SAVE, "Datei zu groß zum Speichern"),
831 (
832 k::ERR_USER_EXISTS,
833 "ein Benutzer mit diesem Namen existiert bereits",
834 ),
835 (k::ERR_USER_NOT_FOUND, "Benutzer nicht gefunden"),
836 (
837 "exclude_dup_err",
838 "Dieser Ordner ist bereits ausgeschlossen.",
839 ),
840 ("exclude_folder", "Ordner ausschließen…"),
841 ("exclude_here", "Diesen Ordner ausschließen"),
842 (
843 "exclude_root_err",
844 "Das gesamte Stammverzeichnis kann nicht ausgeschlossen werden; das würde die Suche abschalten.",
845 ),
846 ("expires", "Läuft ab"),
847 ("expires_at", "Läuft ab: {}"),
848 ("expiry_1d", "1 Tag"),
849 ("expiry_1h", "1 Stunde"),
850 ("expiry_1mo", "1 Monat"),
851 ("expiry_1w", "1 Woche"),
852 ("file", "Datei"),
853 ("file_changed", "Datei auf der Festplatte geändert"),
854 ("file_created", "Datei erstellt"),
855 ("file_info", "Datei-Info"),
856 ("file_name", "Dateiname"),
857 ("files", "Dateien"),
858 ("fmt_tar", "Tar"),
859 ("fmt_targz", "Tar + gzip"),
860 ("fmt_tarzst", "Tar + zstd"),
861 ("fmt_zip", "Zip"),
862 ("folder", "Ordner"),
863 ("folder_created", "Ordner erstellt"),
864 (
865 "folder_gone_msg",
866 "Dieser Ordner ist nicht mehr verfügbar. Wählen Sie links einen anderen aus.",
867 ),
868 ("folder_gone_title", "Ordner nicht verfügbar"),
869 ("folder_name", "Ordnername"),
870 ("folders", "Ordner"),
871 (
872 "folders_paths_hint",
873 "Pfade sind relativ zur Serverwurzel. „.“ ist der gesamte Wurzelordner.",
874 ),
875 ("grid_view", "Kachelansicht"),
876 ("info", "Info"),
877 ("keep_editing", "Weiter bearbeiten"),
878 ("lang_auto", "Automatisch"),
879 ("lang_de", "Deutsch"),
880 ("lang_en", "Englisch"),
881 ("lang_fr", "Französisch"),
882 ("language", "Sprache"),
883 (
884 "language_desc",
885 "Die Sprache der Benutzeroberfläche. „Automatisch“ verwendet die Sprache Ihres Browsers.",
886 ),
887 ("link_copied", "Link in die Zwischenablage kopiert"),
888 (
889 "list_truncated",
890 "Nur die ersten {} Einträge werden angezeigt.",
891 ),
892 ("list_view", "Listenansicht"),
893 ("load_latest", "Neueste Version laden"),
894 ("loading", "Wird geladen…"),
895 ("loading_share", "Freigabe wird geladen…"),
896 ("location", "Ort"),
897 ("log_out", "Abmelden"),
898 ("login_error", "Bitte Name und Passwort eingeben."),
899 ("login_subtitle", "Anmelden, um fortzufahren."),
900 ("manage", "Verwalten"),
901 ("menu", "Menü"),
902 ("meta_created", "erstellt: {}"),
903 ("meta_expires", "läuft ab: {}"),
904 ("mode_ro", "nur lesen"),
905 ("mode_rw", "lesen/schreiben"),
906 ("modified", "Geändert"),
907 (
908 "modified_after",
909 "Diese Datei wurde nach dem Öffnen geändert. „Neueste Version laden“ verwirft Ihre ungespeicherten Änderungen; „Überschreiben“ behält Ihre Version bei.",
910 ),
911 ("more_actions", "Weitere Aktionen"),
912 ("move", "Verschieben"),
913 ("move_here", "Hierher verschieben"),
914 ("move_n_to", "{} Elemente verschieben nach…"),
915 ("move_to", "„{}“ verschieben nach…"),
916 ("moved", "Verschoben"),
917 ("name", "Name"),
918 ("name_required", "Ein Name ist erforderlich."),
919 ("name_taken", "Name wird bereits verwendet"),
920 (
921 "need_folder",
922 "Dem Benutzer muss mindestens ein Ordner zugewiesen werden.",
923 ),
924 ("never", "Nie"),
925 ("never_expires", "Läuft nie ab"),
926 ("new_file", "Neue Datei"),
927 ("new_folder", "Neuer Ordner"),
928 ("new_name", "Neuer Name"),
929 ("new_password", "Neues Passwort"),
930 (
931 "new_password_hint",
932 "Leer lassen, um das aktuelle Passwort zu behalten.",
933 ),
934 (
935 "new_password_keep",
936 "Neues Passwort (leer lassen, um beizubehalten)",
937 ),
938 ("new_user", "Neuer Benutzer"),
939 (
940 "no_folders_msg",
941 "Ein Administrator hat Ihnen noch keine Ordner zugewiesen.",
942 ),
943 ("no_folders_title", "Keine Ordner verfügbar"),
944 ("no_passkeys", "Noch keine Passkeys."),
945 (
946 "no_password_set",
947 "Dieses Konto meldet sich nur mit Passkeys an.",
948 ),
949 ("no_preview", "Keine Vorschau verfügbar"),
950 ("no_preview_msg", "Die App kann „{}“ hier nicht anzeigen."),
951 ("no_shares", "Noch keine Freigaben"),
952 ("no_subfolders", "Keine Unterordner"),
953 ("no_users", "Keine Benutzer."),
954 ("open", "Öffnen"),
955 ("open_in_new_tab", "In neuem Tab öffnen"),
956 ("open_menu", "Menü öffnen"),
957 ("or_label", "oder"),
958 ("overwrite", "Überschreiben"),
959 (
960 "overwrite_question",
961 "Die folgenden Dateien existieren bereits. Überschreiben?",
962 ),
963 ("passkey_added", "Passkey hinzugefügt"),
964 ("passkey_added_on", "Hinzugefügt {}"),
965 ("passkey_label", "Name für diesen Passkey"),
966 ("passkey_last_used", "Zuletzt benutzt {}"),
967 ("passkey_needs_name", "Braucht Ihren Benutzernamen"),
968 (
969 "passkey_needs_name_hint",
970 "Dieser Passkey liegt nicht im Authenticator selbst, der Server muss ihn nachschlagen. Geben Sie Ihren Benutzernamen auf der Anmeldeseite ein, bevor Sie ihn verwenden.",
971 ),
972 ("passkey_never_used", "Nie benutzt"),
973 ("passkey_not_used", "Es wurde kein Passkey verwendet."),
974 ("passkey_removed", "Passkey entfernt"),
975 (
976 "passkey_unsupported",
977 "Dieser Browser kann keine Passkeys verwenden.",
978 ),
979 ("passkeys", "Passkeys"),
980 (
981 "passkeys_hint",
982 "Ein Passkey meldet Sie mit Fingerabdruck, Gesicht oder Sicherheitsschlüssel an.",
983 ),
984 ("password", "Passwort"),
985 ("password_removed", "Passwort entfernt"),
986 (
987 "password_resets_signin",
988 "Ein neues Passwort löscht auch die Passkeys dieses Kontos und hebt eine Zwei-Faktor-Pflicht auf.",
989 ),
990 ("profile", "Profil"),
991 (
992 "profile_save_err",
993 "Profil konnte nicht gespeichert werden: {}",
994 ),
995 ("profile_saved", "Profil gespeichert"),
996 (
997 "pw_short",
998 "Das Passwort muss mindestens 8 Zeichen lang sein.",
999 ),
1000 ("read_only_badge", "Schreibgeschützt"),
1001 ("read_only_hint", "Schreibgeschützter Ordner"),
1002 ("refresh", "Aktualisieren"),
1003 ("refresh_folders", "Ordner aktualisieren"),
1004 ("remove_folder", "Ordner entfernen"),
1005 ("remove_password", "Passwort entfernen"),
1006 (
1007 "remove_password_desc",
1008 "Nur noch mit Passkeys anmelden. Braucht mindestens einen Passkey.",
1009 ),
1010 ("rename", "Umbenennen"),
1011 ("rename_title", "„{}“ umbenennen"),
1012 ("renamed", "Umbenannt"),
1013 ("repeat_password", "Passwort wiederholen"),
1014 ("retry", "Erneut versuchen"),
1015 ("root_dup_err", "Dieser Ordner ist bereits in der Liste."),
1016 (
1017 "rw_disabled",
1018 "Schreibbare Freigaben sind vom Server deaktiviert.",
1019 ),
1020 (
1021 "rw_ro_folder",
1022 "Dieser Ordner ist schreibgeschützt, daher ist die Freigabe auch schreibgeschützt.",
1023 ),
1024 ("save", "Speichern"),
1025 ("save_profile", "Profil speichern"),
1026 ("save_settings", "Einstellungen speichern"),
1027 ("saved", "Gespeichert"),
1028 ("saving", "Wird gespeichert…"),
1029 ("search", "Suche"),
1030 ("search_both", "Beides"),
1031 ("search_collapse_all", "Alle einklappen"),
1032 ("search_content", "Inhalt"),
1033 ("search_excludes", "Von der Suche ausgeschlossen"),
1034 ("search_excludes_empty", "Keine Ordner ausgeschlossen."),
1035 ("search_expand_all", "Alle ausklappen"),
1036 ("search_failed", "Die Suche ist fehlgeschlagen."),
1037 ("search_goto", "Im Ordner anzeigen"),
1038 ("search_here", "Hier suchen"),
1039 ("search_in", "Suchen in…"),
1040 ("search_in_folder", "In diesem Ordner suchen"),
1041 ("search_in_n_files", "in {} Dateien"),
1042 ("search_n_matches", "{} Treffer"),
1043 ("search_n_results", "{} Treffer"),
1044 ("search_name", "Name"),
1045 ("search_no_results", "keine Treffer"),
1046 (
1047 "search_placeholder",
1048 "Dateien und deren Inhalte durchsuchen",
1049 ),
1050 ("search_run", "Suchen"),
1051 ("search_scanned", "{} Dateien durchsucht"),
1052 ("search_show_more", "{} weitere anzeigen"),
1053 ("search_shown_of", "{} von {} angezeigt"),
1054 ("search_skipped", "{} übersprungen (über 10 MiB)"),
1055 ("search_stop", "Stopp"),
1056 ("search_stopped", "gestoppt · {} Treffer"),
1057 ("search_took", "{} ms"),
1058 ("searching", "Suche läuft…"),
1059 (
1060 "second_factor_passkey",
1061 "Bestätigen Sie jetzt mit Ihrem Passkey.",
1062 ),
1063 ("second_factor_password", "Passwort für {} eingeben."),
1064 ("security", "Sicherheit"),
1065 (
1066 "select_folder_msg",
1067 "Wählen Sie links einen Ordner aus, um mit dem Durchsuchen zu beginnen.",
1068 ),
1069 ("select_folder_title", "Ordner auswählen"),
1070 ("server", "Server"),
1071 ("server_root", "Serverwurzel"),
1072 ("server_unreachable", "Der Server ist nicht erreichbar."),
1073 ("settings", "Einstellungen"),
1074 (
1075 "settings_save_err",
1076 "Einstellungen konnten nicht gespeichert werden: {}",
1077 ),
1078 ("settings_saved", "Einstellungen gespeichert"),
1079 (
1080 "setup_intro",
1081 "Erster Start: Erstellen Sie das Administrator-Konto. Dieser Benutzer kann andere Benutzer und die Servereinstellungen verwalten.",
1082 ),
1083 (
1084 "setup_name_err",
1085 "Bitte wählen Sie einen Namen (1–64 Zeichen).",
1086 ),
1087 ("setup_pw_mismatch", "Die Passwörter stimmen nicht überein."),
1088 ("share", "Teilen"),
1089 (
1090 "share_create_err",
1091 "Freigabe konnte nicht erstellt werden: {}",
1092 ),
1093 ("share_delete_err", "Freigabe konnte nicht gelöscht werden"),
1094 ("share_deleted", "Freigabe gelöscht"),
1095 (
1096 "share_dialog_hint",
1097 "Erstellt einen Link für diesen Eintrag. Mit schreibbaren Links können Empfänger Dateien hochladen und bearbeiten.",
1098 ),
1099 ("share_expired", "Freigabe abgelaufen"),
1100 (
1101 "share_expired_msg",
1102 "Dieser Freigabelink ist nicht mehr aktiv.",
1103 ),
1104 ("share_link", "Freigabelink"),
1105 ("share_locked_title", "Passwort erforderlich"),
1106 ("share_not_found", "Freigabe nicht gefunden"),
1107 (
1108 "share_not_found_msg",
1109 "Dieser Link ist ungültig oder die Freigabe wurde entfernt.",
1110 ),
1111 ("share_owner_disabled", "deaktiviertes Konto"),
1112 (
1113 "share_password_hint",
1114 "Optional. Besucher müssen es eingeben, bevor die Freigabe geöffnet wird, mindestens 8 Zeichen.",
1115 ),
1116 ("share_perm_ro", "Jeder mit diesem Link kann „{}“ lesen."),
1117 (
1118 "share_perm_rw",
1119 "Jeder mit diesem Link kann „{}“ lesen und schreiben.",
1120 ),
1121 ("share_protected", "Passwortgeschützt"),
1122 ("share_title", "{} teilen"),
1123 ("shares", "Freigaben"),
1124 (
1125 "shares_delete_err",
1126 "Freigaben konnten nicht gelöscht werden",
1127 ),
1128 (
1129 "shares_empty_hint",
1130 "Klicken Sie mit der rechten Maustaste auf eine Datei oder einen Ordner und wählen Sie „Teilen“, um einen Link zu erstellen.",
1131 ),
1132 (
1133 "shares_view_hint",
1134 "Von Ihnen erstellte Links. Klicken Sie mit der rechten Maustaste auf eine Datei oder einen Ordner und wählen Sie „Teilen“, um einen hinzuzufügen.",
1135 ),
1136 ("sign_in", "Anmelden"),
1137 ("signin_both", "Passwort und Passkey"),
1138 (
1139 "signin_both_desc",
1140 "Beides ist nötig. WebDAV-Einbindungen funktionieren dann nicht mehr, weil sie nur ein Passwort senden können.",
1141 ),
1142 ("signin_either", "Passwort oder Passkey"),
1143 (
1144 "signin_either_desc",
1145 "Eines davon allein genügt zur Anmeldung.",
1146 ),
1147 ("signin_requirement", "Anmeldung verlangt"),
1148 (
1149 "single_click_desc",
1150 "Wenn aktiv, öffnet ein einfacher Klick einen Eintrag und Strg+Klick wählt ihn aus. Wenn inaktiv, wählt ein einfacher Klick aus und ein Doppelklick öffnet.",
1151 ),
1152 ("single_click_label", "Einzelklick öffnet Einträge"),
1153 ("size", "Größe"),
1154 ("some_files_exist", "Einige Dateien existieren bereits"),
1155 ("sort_asc", "Aufsteigend"),
1156 ("sort_by", "Sortieren nach"),
1157 ("sort_desc", "Absteigend"),
1158 ("theme_auto", "Auto"),
1159 ("theme_dark", "Dunkel"),
1160 ("theme_light", "Hell"),
1161 ("theme_title", "Design: {} (klicken zum Wechseln)"),
1162 (
1163 "thumbnails_desc",
1164 "Für Bilder und Videos in der Kachelansicht eine kleine Vorschau statt eines Symbols zeigen.",
1165 ),
1166 ("thumbnails_label", "Vorschaubilder zeigen"),
1167 ("type", "Typ"),
1168 ("unlock", "Freigabe öffnen"),
1169 ("unsaved_changes", "Ungespeicherte Änderungen"),
1170 ("until", "Bis"),
1171 ("upload_abort", "Abbrechen"),
1172 ("upload_aborted", "Abgebrochen"),
1173 ("upload_active_n", "{} aktiv"),
1174 (
1175 "upload_apply_all",
1176 "Auch auf Dateien anwenden, die während des Uploads entstehen",
1177 ),
1178 (
1179 "upload_check_failed",
1180 "Prüfung auf vorhandene Dateien fehlgeschlagen: {}",
1181 ),
1182 ("upload_clear", "Aus der Liste entfernen"),
1183 ("upload_clear_finished", "Abgeschlossene entfernen"),
1184 ("upload_collapse", "Einklappen"),
1185 ("upload_complete", "Hochladen abgeschlossen"),
1186 (
1187 "upload_conflict_question",
1188 "Die folgenden Dateien existieren bereits. Überschreiben oder überspringen?",
1189 ),
1190 ("upload_drop_hint", "Zum Hochladen hier ablegen"),
1191 ("upload_errors_n", "{} fehlgeschlagen"),
1192 (
1193 "upload_exists_ask",
1194 "„{}“ ist während des Uploads auf dem Server entstanden.",
1195 ),
1196 ("upload_expand", "Ausklappen"),
1197 ("upload_failed", "Fehlgeschlagen"),
1198 ("upload_files", "Dateien hochladen"),
1199 ("upload_folder", "Ordner hochladen"),
1200 ("upload_n_files", "{} Dateien"),
1201 ("upload_pause", "Pausieren"),
1202 ("upload_paused", "Pausiert"),
1203 ("upload_preparing", "Wird vorbereitet…"),
1204 ("upload_resume", "Fortsetzen"),
1205 ("upload_show_graph", "Details und Geschwindigkeitsverlauf"),
1206 ("upload_skip", "Überspringen"),
1207 ("upload_skipped_n", "{} übersprungen"),
1208 ("upload_speed", "{}/s"),
1209 ("upload_waiting", "Wartet auf Ihre Antwort"),
1210 ("uploads", "Uploads"),
1211 ("uploads_empty", "Noch keine Uploads"),
1212 ("use_passkey", "Passkey verwenden"),
1213 (
1214 "use_passkey_hint",
1215 "Lassen Sie den Namen leer, außer Ihr Passkey braucht ihn.",
1216 ),
1217 ("user_created", "Benutzer erstellt"),
1218 ("user_delete_err", "Benutzer konnte nicht gelöscht werden"),
1219 (
1220 "user_save_err",
1221 "Benutzer konnte nicht gespeichert werden: {}",
1222 ),
1223 ("user_updated", "Benutzer aktualisiert"),
1224 ("users", "Benutzer"),
1225 ("your_shares", "Ihre Freigaben"),
1226];
1227
1228/// French translations.
1229const FR: &[(&str, &str)] = &[
1230 ("active_can_signin", "Actif (peut se connecter)"),
1231 ("add_folder", "Ajouter un dossier…"),
1232 ("add_here", "Ajouter ce dossier"),
1233 ("add_passkey", "Ajouter une clé d'accès"),
1234 ("administrator", "Administrateur"),
1235 ("all_shares", "Tous les partages"),
1236 (
1237 "all_shares_hint",
1238 "Tous les liens de ce serveur, groupés par le compte qui les a créés.",
1239 ),
1240 (
1241 "allow_editing",
1242 "Autoriser la modification (lecture-écriture)",
1243 ),
1244 ("allow_rw_shares", "Autoriser les partages modifiables"),
1245 (
1246 "allow_rw_shares_desc",
1247 "Autoriser les utilisateurs à créer des liens de partage en lecture-écriture. Désactivé par défaut.",
1248 ),
1249 ("and_more", "…et {} autres"),
1250 ("cancel", "Annuler"),
1251 (
1252 "cant_delete_self",
1253 "Vous ne pouvez pas supprimer votre propre compte",
1254 ),
1255 ("close", "Fermer"),
1256 ("close_esc", "Fermer (Échap)"),
1257 ("copied", "Copié"),
1258 ("copy", "Copier"),
1259 ("copy_dav_link", "Copier le lien WebDAV"),
1260 ("copy_here", "Copier ici"),
1261 ("copy_link", "Copier le lien"),
1262 ("copy_manual", "Copiez le lien manuellement"),
1263 ("copy_n_to", "Copier {} éléments vers…"),
1264 ("copy_to", "Copier « {} » vers…"),
1265 ("create", "Créer"),
1266 ("create_admin", "Créer le compte administrateur"),
1267 ("create_link", "Créer le lien"),
1268 ("create_user", "Créer l'utilisateur"),
1269 ("creating", "Création…"),
1270 ("custom", "Personnalisé…"),
1271 (
1272 "default_root_desc",
1273 "Ouvrir ce dossier au chargement de la page et via le lien d'accueil.",
1274 ),
1275 ("default_root_label", "Dossier par défaut"),
1276 ("default_root_none", "Aperçu des dossiers"),
1277 ("delete", "Supprimer"),
1278 ("delete_all", "Tout supprimer"),
1279 (
1280 "delete_all_msg",
1281 "Supprimer les {} liens de partage ? Toute personne les possédant perdra l'accès. Cette action est irréversible.",
1282 ),
1283 ("delete_all_title", "Supprimer tous les partages ?"),
1284 (
1285 "delete_all_user_msg",
1286 "Supprimer tous les liens de partage de « {} » ? Toute personne les possédant perdra l'accès. Cette action est irréversible.",
1287 ),
1288 (
1289 "delete_folder_msg",
1290 "Supprimer le dossier « {} » et tout son contenu ?\nCette action est irréversible.",
1291 ),
1292 (
1293 "delete_msg",
1294 "Supprimer « {} » ?\nCette action est irréversible.",
1295 ),
1296 (
1297 "delete_n_msg",
1298 "Supprimer {} éléments ?\nCette action est irréversible.",
1299 ),
1300 (
1301 "delete_partial",
1302 "{} des {} liens n'ont pas pu être supprimés",
1303 ),
1304 ("delete_share", "Supprimer le partage"),
1305 ("delete_user", "Supprimer l'utilisateur"),
1306 (
1307 "delete_user_confirm",
1308 "Supprimer l'utilisateur « {} » ? Cette action est irréversible.",
1309 ),
1310 ("deleted", "Supprimé"),
1311 ("deleted_n_shares", "{} partage(s) supprimé(s)"),
1312 ("discard", "Abandonner"),
1313 (
1314 "discard_question",
1315 "Abandonner vos modifications et fermer l'éditeur ?",
1316 ),
1317 ("dismiss", "Ignorer"),
1318 ("dismiss_error", "Ignorer l'erreur"),
1319 ("done", "Terminé"),
1320 ("download", "Télécharger"),
1321 (
1322 "download_formats_msg",
1323 "Choisissez un format d'archivage pour le dossier.",
1324 ),
1325 ("download_started", "Téléchargement démarré"),
1326 ("download_title", "Télécharger « {} »"),
1327 ("edit", "Modifier"),
1328 ("edit_user_title", "Modifier « {} »"),
1329 ("empty_folder", "Ce dossier est vide."),
1330 (k::ERR_ADMIN_ONLY, "réservé aux administrateurs"),
1331 (k::ERR_ALREADY_SET_UP, "le serveur est déjà configuré"),
1332 (k::ERR_BAD_ACTION, "action=content attendu"),
1333 (k::ERR_BAD_BODY, "corps de requête invalide"),
1334 (k::ERR_BAD_EXPIRES_AT, "la date d'expiration est invalide"),
1335 (
1336 k::ERR_BAD_FORMAT,
1337 "le format doit être zip, tar, tar.gz ou tar.zst",
1338 ),
1339 (
1340 k::ERR_BAD_MULTIPART,
1341 "multipart/form-data avec boundary attendu",
1342 ),
1343 (k::ERR_BAD_PART_NAME, "nom de partie invalide"),
1344 (
1345 k::ERR_BAD_POST,
1346 "POST attend un téléversement multipart, une mutation JSON ou ?action=mkdir/create-file",
1347 ),
1348 (k::ERR_BAD_UPLOAD, "données de téléversement invalides"),
1349 (
1350 k::ERR_BAD_UPLOAD_PATH,
1351 "chemin de fichier invalide dans le téléversement",
1352 ),
1353 (
1354 k::ERR_CHALLENGE_EXPIRED,
1355 "Cela a pris trop de temps. Veuillez réessayer.",
1356 ),
1357 (k::ERR_DST_REQUIRED, "dst_root_id est requis"),
1358 (k::ERR_FILE_NAME_REQUIRED, "un nom de fichier est requis"),
1359 (k::ERR_FILES_EXIST, "certains fichiers existent déjà"),
1360 (k::ERR_FOLDER_EXISTS, "un dossier avec ce nom existe déjà"),
1361 (k::ERR_FOLDER_NAME_REQUIRED, "un nom de dossier est requis"),
1362 (k::ERR_FS_CONFLICT, "existe déjà"),
1363 (k::ERR_FS_FORBIDDEN, "accès refusé"),
1364 (k::ERR_FS_NOT_A_DIR, "ce n'est pas un dossier"),
1365 (k::ERR_FS_NOT_FOUND, "dossier introuvable"),
1366 (k::ERR_FS_ROOT_MISSING, "le dossier configuré n'existe plus"),
1367 (k::ERR_INTERNAL, "erreur interne"),
1368 (k::ERR_INVALID_CREDENTIALS, "nom ou mot de passe incorrect"),
1369 (
1370 k::ERR_INVALID_DEFAULT_ROOT,
1371 "le dossier par défaut ne fait pas partie de vos dossiers",
1372 ),
1373 (k::ERR_INVALID_LANGUAGE, "balise de langue invalide"),
1374 (
1375 k::ERR_LAST_ADMIN,
1376 "impossible de supprimer le dernier administrateur actif",
1377 ),
1378 (
1379 k::ERR_LAST_ADMIN_DELETE,
1380 "impossible de supprimer le dernier administrateur actif",
1381 ),
1382 (
1383 k::ERR_LOCKED_OUT,
1384 "Le compte n'aurait alors plus aucun moyen de connexion.",
1385 ),
1386 (
1387 k::ERR_MODE_NEEDS_PASSKEY,
1388 "Ajoutez une clé d'accès avant d'exiger les deux.",
1389 ),
1390 (
1391 k::ERR_MODE_NEEDS_PASSWORD,
1392 "Définissez un mot de passe avant d'exiger les deux.",
1393 ),
1394 (k::ERR_NAME_LENGTH, "le nom doit contenir 1 à 64 caractères"),
1395 (k::ERR_NEW_NAME_REQUIRED, "new_name est requis"),
1396 (k::ERR_NO_FILES_UPLOADED, "aucun fichier n'a été téléversé"),
1397 (k::ERR_NO_SUCH_FOLDER, "ce dossier n'existe pas"),
1398 (k::ERR_NOT_A_FILE, "ce n'est pas un fichier"),
1399 (k::ERR_NOT_SIGNED_IN, "non connecté"),
1400 (
1401 k::ERR_OWN_ACCOUNT,
1402 "vous ne pouvez pas désactiver votre propre compte",
1403 ),
1404 (
1405 k::ERR_OWN_ADMIN,
1406 "vous ne pouvez pas retirer vos propres droits d'administrateur",
1407 ),
1408 (
1409 k::ERR_OWN_DELETE,
1410 "vous ne pouvez pas supprimer votre propre compte",
1411 ),
1412 (k::ERR_PART_NO_NAME, "partie sans nom"),
1413 (
1414 k::ERR_PASSKEY_DUPLICATE,
1415 "Cette clé d'accès est déjà enregistrée.",
1416 ),
1417 (
1418 k::ERR_PASSKEY_FAILED,
1419 "Cette clé d'accès n'a pas pu être utilisée.",
1420 ),
1421 (
1422 k::ERR_PASSKEY_LAST_CREDENTIAL,
1423 "Définissez un mot de passe avant de retirer votre dernière clé d'accès.",
1424 ),
1425 (
1426 k::ERR_PASSKEY_LIMIT,
1427 "Ce compte a déjà autant de clés d'accès que permis.",
1428 ),
1429 (
1430 k::ERR_PASSKEY_MALFORMED,
1431 "Le navigateur a envoyé des données illisibles.",
1432 ),
1433 (k::ERR_PASSKEY_NOT_FOUND, "Clé d'accès introuvable."),
1434 (
1435 k::ERR_PASSKEY_UNAVAILABLE,
1436 "Les clés d'accès ne sont pas disponibles ici.",
1437 ),
1438 (
1439 k::ERR_PASSWORD_LAST_CREDENTIAL,
1440 "Ajoutez une clé d'accès avant de retirer votre mot de passe.",
1441 ),
1442 (
1443 k::ERR_PASSWORD_SHORT,
1444 "le mot de passe doit contenir au moins 8 caractères",
1445 ),
1446 (k::ERR_PATH_REQUIRED, "un chemin dans le dossier est requis"),
1447 (k::ERR_READ_ONLY_FOLDER, "dossier en lecture seule"),
1448 (
1449 k::ERR_REQUIRED_BY_MODE,
1450 "Ce compte exige un mot de passe et une clé d'accès.",
1451 ),
1452 (k::ERR_ROOT_FORBIDDEN, "dossier inaccessible"),
1453 (
1454 k::ERR_RW_RO_FOLDER,
1455 "ce dossier est en lecture seule pour vous, il ne peut donc pas être partagé en écriture",
1456 ),
1457 (
1458 k::ERR_RW_SHARES_DISABLED,
1459 "les partages modifiables sont désactivés",
1460 ),
1461 (k::ERR_SAVE_FAILED, "impossible d'enregistrer le fichier"),
1462 (
1463 k::ERR_SEARCH_BAD_SCOPE,
1464 "la portée doit être name, content ou both",
1465 ),
1466 (
1467 k::ERR_SEARCH_FORBIDDEN,
1468 "la recherche nécessite une session connectée",
1469 ),
1470 (
1471 k::ERR_SESSION_EXPIRED,
1472 "session expirée, veuillez vous connecter à nouveau",
1473 ),
1474 (k::ERR_SHARE_EXPIRED, "ce partage a expiré"),
1475 (
1476 k::ERR_SHARE_LOCKED,
1477 "Ce partage est protégé par un mot de passe.",
1478 ),
1479 (
1480 k::ERR_SHARE_NO_PASSWORD,
1481 "Ce partage n'a pas de mot de passe.",
1482 ),
1483 (k::ERR_SHARE_NOT_FOUND, "partage introuvable"),
1484 (
1485 k::ERR_SHARE_TOKEN_FORBIDDEN,
1486 "un jeton de partage ne peut pas être utilisé ici ; connectez-vous à la place",
1487 ),
1488 (k::ERR_SHARE_WRONG_PASSWORD, "Mot de passe incorrect."),
1489 (
1490 k::ERR_TOO_LARGE_PREVIEW,
1491 "fichier trop volumineux pour l'aperçu",
1492 ),
1493 (
1494 k::ERR_TOO_LARGE_SAVE,
1495 "fichier trop volumineux pour être enregistré",
1496 ),
1497 (k::ERR_USER_EXISTS, "un utilisateur avec ce nom existe déjà"),
1498 (k::ERR_USER_NOT_FOUND, "utilisateur introuvable"),
1499 ("exclude_dup_err", "Ce dossier est déjà exclu."),
1500 ("exclude_folder", "Exclure un dossier…"),
1501 ("exclude_here", "Exclure ce dossier"),
1502 (
1503 "exclude_root_err",
1504 "La racine entière ne peut pas être exclue ; cela désactiverait la recherche.",
1505 ),
1506 ("expires", "Expire"),
1507 ("expires_at", "Expire le {}"),
1508 ("expiry_1d", "1 jour"),
1509 ("expiry_1h", "1 heure"),
1510 ("expiry_1mo", "1 mois"),
1511 ("expiry_1w", "1 semaine"),
1512 ("file", "Fichier"),
1513 ("file_changed", "Fichier modifié sur disque"),
1514 ("file_created", "Fichier créé"),
1515 ("file_info", "Informations"),
1516 ("file_name", "Nom du fichier"),
1517 ("files", "Fichiers"),
1518 ("fmt_tar", "Tar"),
1519 ("fmt_targz", "Tar + gzip"),
1520 ("fmt_tarzst", "Tar + zstd"),
1521 ("fmt_zip", "Zip"),
1522 ("folder", "Dossier"),
1523 ("folder_created", "Dossier créé"),
1524 (
1525 "folder_gone_msg",
1526 "Ce dossier n'est plus accessible. Choisissez-en un autre dans le panneau latéral.",
1527 ),
1528 ("folder_gone_title", "Dossier indisponible"),
1529 ("folder_name", "Nom du dossier"),
1530 ("folders", "Dossiers"),
1531 (
1532 "folders_paths_hint",
1533 "Les chemins sont relatifs à la racine du serveur. « . » désigne toute la racine.",
1534 ),
1535 ("grid_view", "Vue en grille"),
1536 ("info", "Infos"),
1537 ("keep_editing", "Continuer à modifier"),
1538 ("lang_auto", "Automatique"),
1539 ("lang_de", "Allemand"),
1540 ("lang_en", "Anglais"),
1541 ("lang_fr", "Français"),
1542 ("language", "Langue"),
1543 (
1544 "language_desc",
1545 "La langue de l'interface. « Automatique » utilise la langue de votre navigateur.",
1546 ),
1547 ("link_copied", "Lien copié dans le presse-papiers"),
1548 (
1549 "list_truncated",
1550 "Seules les {} premières entrées sont affichées.",
1551 ),
1552 ("list_view", "Vue en liste"),
1553 ("load_latest", "Charger la version la plus récente"),
1554 ("loading", "Chargement…"),
1555 ("loading_share", "Chargement du partage…"),
1556 ("location", "Emplacement"),
1557 ("log_out", "Se déconnecter"),
1558 (
1559 "login_error",
1560 "Veuillez saisir votre nom et votre mot de passe.",
1561 ),
1562 ("login_subtitle", "Connectez-vous pour continuer."),
1563 ("manage", "Gérer"),
1564 ("menu", "Menu"),
1565 ("meta_created", "créé le {}"),
1566 ("meta_expires", "expire le {}"),
1567 ("mode_ro", "lecture seule"),
1568 ("mode_rw", "lecture-écriture"),
1569 ("modified", "Modifié"),
1570 (
1571 "modified_after",
1572 "Ce fichier a été modifié après son ouverture. « Charger la version la plus récente » abandonne vos modifications non enregistrées ; « Écraser » conserve votre version.",
1573 ),
1574 ("more_actions", "Plus d'actions"),
1575 ("move", "Déplacer"),
1576 ("move_here", "Déplacer ici"),
1577 ("move_n_to", "Déplacer {} éléments vers…"),
1578 ("move_to", "Déplacer « {} » vers…"),
1579 ("moved", "Déplacé"),
1580 ("name", "Nom"),
1581 ("name_required", "Un nom est requis."),
1582 ("name_taken", "Ce nom est déjà utilisé"),
1583 ("need_folder", "Donnez au moins un dossier à l'utilisateur."),
1584 ("never", "Jamais"),
1585 ("never_expires", "N'expire jamais"),
1586 ("new_file", "Nouveau fichier"),
1587 ("new_folder", "Nouveau dossier"),
1588 ("new_name", "Nouveau nom"),
1589 ("new_password", "Nouveau mot de passe"),
1590 (
1591 "new_password_hint",
1592 "Laissez vide pour conserver votre mot de passe actuel.",
1593 ),
1594 (
1595 "new_password_keep",
1596 "Nouveau mot de passe (laisser vide pour conserver)",
1597 ),
1598 ("new_user", "Nouvel utilisateur"),
1599 (
1600 "no_folders_msg",
1601 "Aucun administrateur ne vous a encore attribué de dossier.",
1602 ),
1603 ("no_folders_title", "Aucun dossier disponible"),
1604 ("no_passkeys", "Aucune clé d'accès pour l'instant."),
1605 (
1606 "no_password_set",
1607 "Ce compte se connecte uniquement avec des clés d'accès.",
1608 ),
1609 ("no_preview", "Aperçu non disponible"),
1610 (
1611 "no_preview_msg",
1612 "L'application ne peut pas afficher « {} » ici.",
1613 ),
1614 ("no_shares", "Aucun partage pour l'instant"),
1615 ("no_subfolders", "Aucun sous-dossier"),
1616 ("no_users", "Aucun utilisateur."),
1617 ("open", "Ouvrir"),
1618 ("open_in_new_tab", "Ouvrir dans un nouvel onglet"),
1619 ("open_menu", "Ouvrir le menu"),
1620 ("or_label", "ou"),
1621 ("overwrite", "Écraser"),
1622 (
1623 "overwrite_question",
1624 "Les fichiers suivants existent déjà. Les écraser ?",
1625 ),
1626 ("passkey_added", "Clé d'accès ajoutée"),
1627 ("passkey_added_on", "Ajoutée {}"),
1628 ("passkey_label", "Nom de cette clé d'accès"),
1629 ("passkey_last_used", "Dernière utilisation {}"),
1630 ("passkey_needs_name", "Exige votre nom d'utilisateur"),
1631 (
1632 "passkey_needs_name_hint",
1633 "Cette clé d'accès n'est pas stockée dans l'authentificateur, le serveur doit la retrouver. Saisissez votre nom d'utilisateur sur la page de connexion avant de l'utiliser.",
1634 ),
1635 ("passkey_never_used", "Jamais utilisée"),
1636 ("passkey_not_used", "Aucune clé d'accès n'a été utilisée."),
1637 ("passkey_removed", "Clé d'accès retirée"),
1638 (
1639 "passkey_unsupported",
1640 "Ce navigateur ne peut pas utiliser de clés d'accès.",
1641 ),
1642 ("passkeys", "Clés d'accès"),
1643 (
1644 "passkeys_hint",
1645 "Une clé d'accès vous connecte avec votre empreinte, votre visage ou une clé de sécurité.",
1646 ),
1647 ("password", "Mot de passe"),
1648 ("password_removed", "Mot de passe retiré"),
1649 (
1650 "password_resets_signin",
1651 "Définir un mot de passe supprime aussi les clés d'accès de ce compte et lève toute exigence à deux facteurs.",
1652 ),
1653 ("profile", "Profil"),
1654 (
1655 "profile_save_err",
1656 "Impossible d'enregistrer le profil : {}",
1657 ),
1658 ("profile_saved", "Profil enregistré"),
1659 (
1660 "pw_short",
1661 "Le mot de passe doit contenir au moins 8 caractères.",
1662 ),
1663 ("read_only_badge", "Lecture seule"),
1664 ("read_only_hint", "Dossier en lecture seule"),
1665 ("refresh", "Actualiser"),
1666 ("refresh_folders", "Actualiser vos dossiers"),
1667 ("remove_folder", "Retirer le dossier"),
1668 ("remove_password", "Retirer le mot de passe"),
1669 (
1670 "remove_password_desc",
1671 "Se connecter uniquement avec des clés d'accès. Exige au moins une clé d'accès.",
1672 ),
1673 ("rename", "Renommer"),
1674 ("rename_title", "Renommer « {} »"),
1675 ("renamed", "Renommé"),
1676 ("repeat_password", "Répéter le mot de passe"),
1677 ("retry", "Réessayer"),
1678 ("root_dup_err", "Ce dossier est déjà dans la liste."),
1679 (
1680 "rw_disabled",
1681 "Les partages modifiables sont désactivés par le serveur.",
1682 ),
1683 (
1684 "rw_ro_folder",
1685 "Ce dossier est en lecture seule, le partage l'est donc aussi.",
1686 ),
1687 ("save", "Enregistrer"),
1688 ("save_profile", "Enregistrer le profil"),
1689 ("save_settings", "Enregistrer les paramètres"),
1690 ("saved", "Enregistré"),
1691 ("saving", "Enregistrement…"),
1692 ("search", "Rechercher"),
1693 ("search_both", "Les deux"),
1694 ("search_collapse_all", "Tout replier"),
1695 ("search_content", "Contenu"),
1696 ("search_excludes", "Exclu de la recherche"),
1697 ("search_excludes_empty", "Aucun dossier exclu."),
1698 ("search_expand_all", "Tout déplier"),
1699 ("search_failed", "La recherche a échoué."),
1700 ("search_goto", "Afficher dans le dossier"),
1701 ("search_here", "Rechercher ici"),
1702 ("search_in", "Rechercher dans…"),
1703 ("search_in_folder", "Rechercher dans ce dossier"),
1704 ("search_in_n_files", "dans {} fichiers"),
1705 ("search_n_matches", "{} correspondances"),
1706 ("search_n_results", "{} résultats"),
1707 ("search_name", "Nom"),
1708 ("search_no_results", "aucun résultat"),
1709 (
1710 "search_placeholder",
1711 "Rechercher dans les fichiers et leur contenu",
1712 ),
1713 ("search_run", "Rechercher"),
1714 ("search_scanned", "{} fichiers parcourus"),
1715 ("search_show_more", "Afficher {} de plus"),
1716 ("search_shown_of", "{} sur {} affichés"),
1717 ("search_skipped", "{} ignorés (plus de 10 Mio)"),
1718 ("search_stop", "Arrêter"),
1719 ("search_stopped", "arrêté · {} résultats"),
1720 ("search_took", "{} ms"),
1721 ("searching", "recherche…"),
1722 (
1723 "second_factor_passkey",
1724 "Confirmez maintenant avec votre clé d'accès.",
1725 ),
1726 ("second_factor_password", "Saisissez le mot de passe de {}."),
1727 ("security", "Sécurité"),
1728 (
1729 "select_folder_msg",
1730 "Choisissez un dossier dans le panneau latéral pour commencer à naviguer.",
1731 ),
1732 ("select_folder_title", "Choisir un dossier"),
1733 ("server", "Serveur"),
1734 ("server_root", "Racine du serveur"),
1735 ("server_unreachable", "Le serveur est injoignable."),
1736 ("settings", "Paramètres"),
1737 (
1738 "settings_save_err",
1739 "Impossible d'enregistrer les paramètres : {}",
1740 ),
1741 ("settings_saved", "Paramètres enregistrés"),
1742 (
1743 "setup_intro",
1744 "Premier démarrage : créez le compte administrateur. Cet utilisateur peut gérer les autres utilisateurs et les paramètres du serveur.",
1745 ),
1746 (
1747 "setup_name_err",
1748 "Veuillez choisir un nom (1 à 64 caractères).",
1749 ),
1750 (
1751 "setup_pw_mismatch",
1752 "Les mots de passe ne correspondent pas.",
1753 ),
1754 ("share", "Partager"),
1755 ("share_create_err", "Impossible de créer le partage : {}"),
1756 ("share_delete_err", "Impossible de supprimer le partage"),
1757 ("share_deleted", "Partage supprimé"),
1758 (
1759 "share_dialog_hint",
1760 "Crée un lien pour cet élément. Les liens modifiables permettent aux destinataires de téléverser et de modifier.",
1761 ),
1762 ("share_expired", "Partage expiré"),
1763 ("share_expired_msg", "Ce lien de partage n'est plus actif."),
1764 ("share_link", "Lien de partage"),
1765 ("share_locked_title", "Mot de passe requis"),
1766 ("share_not_found", "Partage introuvable"),
1767 (
1768 "share_not_found_msg",
1769 "Ce lien est invalide ou le partage a été supprimé.",
1770 ),
1771 ("share_owner_disabled", "compte désactivé"),
1772 (
1773 "share_password_hint",
1774 "Facultatif. Les visiteurs doivent le saisir avant d'ouvrir le partage, au moins 8 caractères.",
1775 ),
1776 (
1777 "share_perm_ro",
1778 "Toute personne possédant ce lien peut lire « {} ».",
1779 ),
1780 (
1781 "share_perm_rw",
1782 "Toute personne possédant ce lien peut lire et modifier « {} ».",
1783 ),
1784 ("share_protected", "Protégé par mot de passe"),
1785 ("share_title", "Partager {}"),
1786 ("shares", "Partages"),
1787 ("shares_delete_err", "Impossible de supprimer les partages"),
1788 (
1789 "shares_empty_hint",
1790 "Faites un clic droit sur un fichier ou un dossier, puis choisissez « Partager » pour créer un lien.",
1791 ),
1792 (
1793 "shares_view_hint",
1794 "Liens que vous avez créés. Faites un clic droit sur un fichier ou un dossier, puis choisissez « Partager » pour en ajouter un.",
1795 ),
1796 ("sign_in", "Se connecter"),
1797 ("signin_both", "Mot de passe et clé d'accès"),
1798 (
1799 "signin_both_desc",
1800 "Les deux sont exigés. Les montages WebDAV cessent de fonctionner, car ils ne peuvent envoyer qu'un mot de passe.",
1801 ),
1802 ("signin_either", "Mot de passe ou clé d'accès"),
1803 (
1804 "signin_either_desc",
1805 "L'un ou l'autre suffit pour se connecter.",
1806 ),
1807 ("signin_requirement", "Exigence de connexion"),
1808 (
1809 "single_click_desc",
1810 "Quand activé, un simple clic ouvre une entrée et Ctrl+clic la sélectionne. Quand désactivé, un simple clic sélectionne et un double-clic ouvre.",
1811 ),
1812 ("single_click_label", "Ouvrir au clic simple"),
1813 ("size", "Taille"),
1814 ("some_files_exist", "Certains fichiers existent déjà"),
1815 ("sort_asc", "Croissant"),
1816 ("sort_by", "Trier par"),
1817 ("sort_desc", "Décroissant"),
1818 ("theme_auto", "Auto"),
1819 ("theme_dark", "Sombre"),
1820 ("theme_light", "Clair"),
1821 ("theme_title", "Thème : {} (cliquer pour changer)"),
1822 (
1823 "thumbnails_desc",
1824 "Afficher un aperçu à la place d'une icône pour les images et les vidéos en vue grille.",
1825 ),
1826 ("thumbnails_label", "Afficher les miniatures"),
1827 ("type", "Type"),
1828 ("unlock", "Ouvrir le partage"),
1829 ("unsaved_changes", "Modifications non enregistrées"),
1830 ("until", "Jusqu'au"),
1831 ("upload_abort", "Interrompre"),
1832 ("upload_aborted", "Interrompu"),
1833 ("upload_active_n", "{} en cours"),
1834 (
1835 "upload_apply_all",
1836 "Appliquer aussi aux fichiers qui apparaissent pendant le téléversement",
1837 ),
1838 (
1839 "upload_check_failed",
1840 "Impossible de vérifier les fichiers existants : {}",
1841 ),
1842 ("upload_clear", "Retirer de la liste"),
1843 ("upload_clear_finished", "Effacer les terminés"),
1844 ("upload_collapse", "Réduire"),
1845 ("upload_complete", "Téléversement terminé"),
1846 (
1847 "upload_conflict_question",
1848 "Les fichiers suivants existent déjà. Les remplacer ou les ignorer ?",
1849 ),
1850 ("upload_drop_hint", "Déposer ici pour téléverser"),
1851 ("upload_errors_n", "{} en échec"),
1852 (
1853 "upload_exists_ask",
1854 "« {} » est apparu sur le serveur pendant le téléversement.",
1855 ),
1856 ("upload_expand", "Développer"),
1857 ("upload_failed", "Échec"),
1858 ("upload_files", "Téléverser des fichiers"),
1859 ("upload_folder", "Téléverser un dossier"),
1860 ("upload_n_files", "{} fichiers"),
1861 ("upload_pause", "Mettre en pause"),
1862 ("upload_paused", "En pause"),
1863 ("upload_preparing", "Préparation…"),
1864 ("upload_resume", "Reprendre"),
1865 ("upload_show_graph", "Détails et graphique de vitesse"),
1866 ("upload_skip", "Ignorer"),
1867 ("upload_skipped_n", "{} ignorés"),
1868 ("upload_speed", "{}/s"),
1869 ("upload_waiting", "En attente de votre réponse"),
1870 ("uploads", "Téléversements"),
1871 ("uploads_empty", "Aucun téléversement pour l'instant"),
1872 ("use_passkey", "Utiliser une clé d'accès"),
1873 (
1874 "use_passkey_hint",
1875 "Laissez le nom vide, sauf si votre clé d'accès en a besoin.",
1876 ),
1877 ("user_created", "Utilisateur créé"),
1878 ("user_delete_err", "Impossible de supprimer l'utilisateur"),
1879 (
1880 "user_save_err",
1881 "Impossible d'enregistrer l'utilisateur : {}",
1882 ),
1883 ("user_updated", "Utilisateur mis à jour"),
1884 ("users", "Utilisateurs"),
1885 ("your_shares", "Vos partages"),
1886];
1887
1888#[cfg(test)]
1889mod tests {
1890 use super::*;
1891
1892 fn keys<'a>(table: &'a [(&'a str, &'a str)]) -> Vec<&'a str> {
1893 table.iter().map(|(k, _)| *k).collect()
1894 }
1895
1896 #[test]
1897 fn tables_are_sorted() {
1898 // `find` binary-searches, so an out-of-order entry is unreachable.
1899 for (name, table) in [("EN", EN), ("DE", DE), ("FR", FR)] {
1900 let ks = keys(table);
1901 let mut sorted = ks.clone();
1902 sorted.sort_unstable();
1903 assert_eq!(ks, sorted, "{name} table is not sorted by key");
1904 }
1905 }
1906
1907 #[test]
1908 fn no_duplicate_keys() {
1909 for (name, table) in [("EN", EN), ("DE", DE), ("FR", FR)] {
1910 let mut ks = keys(table);
1911 ks.sort_unstable();
1912 let dups = ks
1913 .windows(2)
1914 .filter(|w| w[0] == w[1])
1915 .map(|w| w[0])
1916 .collect::<Vec<_>>();
1917 assert!(dups.is_empty(), "{name} duplicates: {dups:?}");
1918 }
1919 }
1920
1921 #[test]
1922 fn translations_cover_all_keys() {
1923 // Full parity: DE and FR must translate every EN key (both
1924 // directions), so no string can silently fall back to English.
1925 let en = keys(EN);
1926 for (name, table) in [("DE", DE), ("FR", FR)] {
1927 let other = keys(table);
1928 assert_eq!(
1929 en.len(),
1930 other.len(),
1931 "{name} has {} keys, EN has {}",
1932 other.len(),
1933 en.len()
1934 );
1935 for k in &other {
1936 assert!(en.contains(k), "{name} key not in EN: {k}");
1937 }
1938 }
1939 }
1940}
1941