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//! Locale precedence: the signed-in user's profile setting (persisted
18//! server-side), else the browser language. `init` + `set_locale` drive a
19//! global signal so every `tr` subscriber updates.
20
21use std::sync::OnceLock;
22
23use leptos::prelude::*;
24use wasm_bindgen::JsCast;
25
26/// A supported UI language.
27#[derive(Clone, Copy, PartialEq, Eq, Debug)]
28pub enum Locale {
29 En,
30 De,
31 Fr,
32}
33
34impl Locale {
35 pub const fn code(self) -> &'static str {
36 match self {
37 Locale::En => "en",
38 Locale::De => "de",
39 Locale::Fr => "fr",
40 }
41 }
42
43 /// Parse a BCP-47-ish tag ("de", "de-AT", "DE") into a supported locale.
44 pub fn from_code(code: &str) -> Option<Self> {
45 let lang = code.split(['-', '_']).next()?.to_ascii_lowercase();
46 match lang.as_str() {
47 "en" => Some(Locale::En),
48 "de" => Some(Locale::De),
49 "fr" => Some(Locale::Fr),
50 _ => None,
51 }
52 }
53
54 /// The browser's language, restricted to the supported locales.
55 pub fn from_browser() -> Option<Self> {
56 let lang = web_sys::window()?.navigator().language()?;
57 Self::from_code(&lang)
58 }
59}
60
61static READ: OnceLock<ReadSignal<Locale>> = OnceLock::new();
62static WRITE: OnceLock<WriteSignal<Locale>> = OnceLock::new();
63
64/// Called once during app setup, before the first UI render.
65pub fn init(locale: Locale) {
66 let (read, write) = signal(locale);
67 let _ = READ.set(read);
68 let _ = WRITE.set(write);
69 apply_doc_lang(locale);
70}
71
72pub fn set_locale(locale: Locale) {
73 if let Some(w) = WRITE.get() {
74 w.set(locale);
75 }
76 apply_doc_lang(locale);
77}
78
79pub fn current() -> Locale {
80 READ.get().map(|r| r.get()).unwrap_or(Locale::En)
81}
82
83/// Like [`current`] but without registering a reactive dependency — for
84/// code that reads the locale only to compare before writing (e.g. the
85/// profile effect in `app.rs`), so the read cannot retrigger itself.
86pub fn current_untracked() -> Locale {
87 READ.get().map(|r| r.get_untracked()).unwrap_or(Locale::En)
88}
89
90/// Keep `<html lang>` in sync for screen readers and the browser UI.
91fn apply_doc_lang(locale: Locale) {
92 if let Some(html) = web_sys::window()
93 .and_then(|w| w.document())
94 .and_then(|d| d.document_element())
95 .and_then(|e| e.dyn_into::<web_sys::HtmlElement>().ok())
96 {
97 let _ = html.set_attribute("lang", locale.code());
98 }
99}
100
101/// Resolve a key for the current locale (non-reactive).
102pub fn t(key: &str) -> &'static str {
103 text(key, current())
104}
105
106/// Resolve a key for the current locale as a reactive closure, for use as a
107/// `view!` child or attribute value.
108pub fn tr(key: &str) -> impl Fn() -> String + Clone {
109 move || t(key).to_string()
110}
111
112/// Like [`t`], but substitutes a single `{}` placeholder in the translation
113/// with `arg`.
114pub fn t_fmt(key: &str, arg: &str) -> String {
115 t(key).replace("{}", arg)
116}
117
118/// Like [`t_fmt`] for translations with two placeholders, filling the first
119/// `{}` with `a` and the second with `b`. Positional, so a locale that needs
120/// the other order has to reword rather than swap.
121pub fn t_fmt2(key: &str, a: &str, b: &str) -> String {
122 let s = t(key);
123 match s.split_once("{}") {
124 Some((head, tail)) => format!("{head}{a}{}", tail.replacen("{}", b, 1)),
125 None => s.to_string(),
126 }
127}
128
129/// Localize a server error: a known code maps to its translation, anything
130/// else (no code, unknown code) falls back to the server's English message.
131pub fn error_text(code: Option<&str>, fallback: &str) -> String {
132 let Some(code) = code.filter(|c| find(EN, c).is_some()) else {
133 return fallback.to_string();
134 };
135 // One-shot lookup for a toast message: no reactive context, so read the
136 // locale untracked.
137 text(code, current_untracked()).to_string()
138}
139
140fn text(key: &str, locale: Locale) -> &'static str {
141 // Keys are compile-time constants from `k`; a miss is a bug (the unit
142 // tests pin the tables against each other).
143 match locale {
144 Locale::En => find(EN, key).expect("unknown i18n key"),
145 Locale::De => find(DE, key)
146 .or_else(|| find(EN, key))
147 .expect("unknown i18n key"),
148 Locale::Fr => find(FR, key)
149 .or_else(|| find(EN, key))
150 .expect("unknown i18n key"),
151 }
152}
153
154fn find(table: &'static [(&'static str, &'static str)], key: &str) -> Option<&'static str> {
155 table.iter().find(|(k, _)| *k == key).map(|(_, v)| *v)
156}
157
158/// Translation keys. English text is the value in `EN` below.
159pub mod k {
160 // Empty states
161 pub const NO_FOLDERS_TITLE: &str = "no_folders_title";
162 pub const NO_FOLDERS_MSG: &str = "no_folders_msg";
163 pub const SELECT_FOLDER_TITLE: &str = "select_folder_title";
164 pub const SELECT_FOLDER_MSG: &str = "select_folder_msg";
165 pub const FOLDER_GONE_TITLE: &str = "folder_gone_title";
166 pub const FOLDER_GONE_MSG: &str = "folder_gone_msg";
167 pub const LOADING: &str = "loading";
168 pub const LOADING_SHARE: &str = "loading_share";
169 pub const RETRY: &str = "retry";
170 pub const EMPTY_FOLDER: &str = "empty_folder";
171
172 // Browser chrome
173 pub const REFRESH: &str = "refresh";
174 pub const GRID_VIEW: &str = "grid_view";
175 pub const LIST_VIEW: &str = "list_view";
176
177 // Entry actions
178 pub const OPEN: &str = "open";
179 pub const OPEN_IN_NEW_TAB: &str = "open_in_new_tab";
180 pub const DOWNLOAD: &str = "download";
181 pub const RENAME: &str = "rename";
182 pub const MOVE: &str = "move";
183 pub const COPY: &str = "copy";
184 pub const DONE: &str = "done";
185 pub const SHARE: &str = "share";
186 pub const INFO: &str = "info";
187 pub const DELETE: &str = "delete";
188 pub const NEW_FOLDER: &str = "new_folder";
189 pub const NEW_FILE: &str = "new_file";
190 pub const UPLOAD_FILES: &str = "upload_files";
191 pub const UPLOAD_FOLDER: &str = "upload_folder";
192 pub const READ_ONLY_HINT: &str = "read_only_hint";
193 pub const READ_ONLY_BADGE: &str = "read_only_badge";
194 pub const MORE_ACTIONS: &str = "more_actions";
195
196 // Dialogs: prompt / confirm
197 pub const FOLDER_NAME: &str = "folder_name";
198 pub const FILE_NAME: &str = "file_name";
199 pub const CREATE: &str = "create";
200 pub const RENAME_TITLE: &str = "rename_title";
201 pub const NEW_NAME: &str = "new_name";
202 pub const DELETE_FOLDER_MSG: &str = "delete_folder_msg";
203 pub const DELETE_MSG: &str = "delete_msg";
204 pub const CANCEL: &str = "cancel";
205
206 // Dialogs: conflict / picker / download format / info
207 pub const OVERWRITE_QUESTION: &str = "overwrite_question";
208 pub const OVERWRITE: &str = "overwrite";
209 pub const AND_MORE: &str = "and_more";
210 pub const NO_SUBFOLDERS: &str = "no_subfolders";
211 pub const MOVE_TO: &str = "move_to";
212 pub const COPY_TO: &str = "copy_to";
213 pub const READ_ONLY_ROOT: &str = "read_only_root";
214 pub const MOVE_HERE: &str = "move_here";
215 pub const COPY_HERE: &str = "copy_here";
216 pub const TOP: &str = "top";
217 pub const DOWNLOAD_TITLE: &str = "download_title";
218 pub const DOWNLOAD_FORMATS_MSG: &str = "download_formats_msg";
219 pub const FMT_ZIP: &str = "fmt_zip";
220 pub const FMT_TAR: &str = "fmt_tar";
221 pub const FMT_TARGZ: &str = "fmt_targz";
222 pub const FMT_TARZST: &str = "fmt_tarzst";
223 pub const FILE_INFO: &str = "file_info";
224 pub const NAME: &str = "name";
225 pub const TYPE: &str = "type";
226 pub const FOLDER: &str = "folder";
227 pub const FILE: &str = "file";
228 pub const SIZE: &str = "size";
229 pub const MODIFIED: &str = "modified";
230 pub const LOCATION: &str = "location";
231 pub const CLOSE: &str = "close";
232
233 // Toasts
234 pub const FOLDER_CREATED: &str = "folder_created";
235 pub const FILE_CREATED: &str = "file_created";
236 pub const NAME_TAKEN: &str = "name_taken";
237 pub const RENAMED: &str = "renamed";
238 pub const DELETED: &str = "deleted";
239 pub const UPLOADING: &str = "uploading";
240 pub const SOME_FILES_EXIST: &str = "some_files_exist";
241 pub const UPLOAD_COMPLETE: &str = "upload_complete";
242 pub const MOVED: &str = "moved";
243 pub const COPIED: &str = "copied";
244 pub const DOWNLOAD_STARTED: &str = "download_started";
245 pub const SAVED: &str = "saved";
246
247 // Shell: sidebar, topbar, footer
248 pub const MENU: &str = "menu";
249 pub const OPEN_MENU: &str = "open_menu";
250 pub const REFRESH_FOLDERS: &str = "refresh_folders";
251 pub const THEME_TITLE: &str = "theme_title";
252 pub const LOG_OUT: &str = "log_out";
253 pub const FILES: &str = "files";
254 pub const MANAGE: &str = "manage";
255 pub const SHARES: &str = "shares";
256 pub const USERS: &str = "users";
257 pub const NO_USERS: &str = "no_users";
258 pub const SETTINGS: &str = "settings";
259 pub const THEME_AUTO: &str = "theme_auto";
260 pub const THEME_LIGHT: &str = "theme_light";
261 pub const THEME_DARK: &str = "theme_dark";
262
263 // Search view
264 pub const SEARCH: &str = "search";
265 pub const SEARCH_PLACEHOLDER: &str = "search_placeholder";
266 pub const SEARCH_NAME: &str = "search_name";
267 pub const SEARCH_CONTENT: &str = "search_content";
268 pub const SEARCH_BOTH: &str = "search_both";
269 pub const SEARCH_GOTO: &str = "search_goto";
270 pub const SEARCH_RUN: &str = "search_run";
271 pub const SEARCH_STOP: &str = "search_stop";
272 pub const SEARCHING: &str = "searching";
273 pub const SEARCH_N_RESULTS: &str = "search_n_results";
274 pub const SEARCH_TOOK: &str = "search_took";
275 pub const SEARCH_STOPPED: &str = "search_stopped";
276 pub const SEARCH_SCANNED: &str = "search_scanned";
277 pub const SEARCH_SKIPPED: &str = "search_skipped";
278 pub const SEARCH_N_MATCHES: &str = "search_n_matches";
279 pub const SEARCH_IN_N_FILES: &str = "search_in_n_files";
280 pub const SEARCH_COLLAPSE_ALL: &str = "search_collapse_all";
281 pub const SEARCH_EXPAND_ALL: &str = "search_expand_all";
282 pub const SEARCH_SHOWN_OF: &str = "search_shown_of";
283 pub const SEARCH_SHOW_MORE: &str = "search_show_more";
284 pub const SEARCH_NO_RESULTS: &str = "search_no_results";
285
286 // Settings
287 pub const SAVING: &str = "saving";
288 pub const PROFILE: &str = "profile";
289 pub const SERVER: &str = "server";
290 pub const PROFILE_SAVED: &str = "profile_saved";
291 pub const PROFILE_SAVE_ERR: &str = "profile_save_err";
292 pub const SETTINGS_SAVED: &str = "settings_saved";
293 pub const SETTINGS_SAVE_ERR: &str = "settings_save_err";
294 pub const SINGLE_CLICK_LABEL: &str = "single_click_label";
295 pub const SINGLE_CLICK_DESC: &str = "single_click_desc";
296 pub const SAVE_PROFILE: &str = "save_profile";
297 pub const ALLOW_RW_SHARES: &str = "allow_rw_shares";
298 pub const ALLOW_RW_SHARES_DESC: &str = "allow_rw_shares_desc";
299 pub const SAVE_SETTINGS: &str = "save_settings";
300 pub const LANGUAGE: &str = "language";
301 pub const LANGUAGE_DESC: &str = "language_desc";
302 pub const LANG_AUTO: &str = "lang_auto";
303 pub const LANG_EN: &str = "lang_en";
304 pub const LANG_DE: &str = "lang_de";
305 pub const LANG_FR: &str = "lang_fr";
306
307 // Admin: users
308 pub const DELETE_USER_CONFIRM: &str = "delete_user_confirm";
309 pub const USER_DELETE_ERR: &str = "user_delete_err";
310 pub const NEW_USER: &str = "new_user";
311 pub const EDIT: &str = "edit";
312 pub const DELETE_USER: &str = "delete_user";
313 pub const CANT_DELETE_SELF: &str = "cant_delete_self";
314 pub const ROOT_DUP_ERR: &str = "root_dup_err";
315 pub const NAME_REQUIRED: &str = "name_required";
316 pub const PW_SHORT: &str = "pw_short";
317 pub const NEED_FOLDER: &str = "need_folder";
318 pub const USER_CREATED: &str = "user_created";
319 pub const USER_UPDATED: &str = "user_updated";
320 pub const USER_SAVE_ERR: &str = "user_save_err";
321 pub const PASSWORD: &str = "password";
322 pub const NEW_PASSWORD_KEEP: &str = "new_password_keep";
323 pub const ADMINISTRATOR: &str = "administrator";
324 pub const ACTIVE_CAN_SIGNIN: &str = "active_can_signin";
325 pub const REMOVE_FOLDER: &str = "remove_folder";
326 pub const CREATE_USER: &str = "create_user";
327 pub const EDIT_USER_TITLE: &str = "edit_user_title";
328 pub const FOLDERS: &str = "folders";
329 pub const FOLDERS_PATHS_HINT: &str = "folders_paths_hint";
330 pub const ADD: &str = "add";
331 pub const MODE_RW: &str = "mode_rw";
332 pub const EXPIRY_1H: &str = "expiry_1h";
333 pub const EXPIRY_1D: &str = "expiry_1d";
334 pub const EXPIRY_1W: &str = "expiry_1w";
335 pub const EXPIRY_1MO: &str = "expiry_1mo";
336 pub const META_EXPIRES: &str = "meta_expires";
337 pub const META_CREATED: &str = "meta_created";
338 pub const MODE_RO: &str = "mode_ro";
339
340 // Share page (public)
341 pub const SHARE_NOT_FOUND: &str = "share_not_found";
342 pub const SHARE_NOT_FOUND_MSG: &str = "share_not_found_msg";
343 pub const SHARE_EXPIRED: &str = "share_expired";
344 pub const SHARE_EXPIRED_MSG: &str = "share_expired_msg";
345
346 // Shares: dialog + management
347 pub const COPY_MANUAL: &str = "copy_manual";
348 pub const LINK_COPIED: &str = "link_copied";
349 pub const SHARE_CREATE_ERR: &str = "share_create_err";
350 pub const SHARE_LINK: &str = "share_link";
351 pub const SHARE_PERM_RW: &str = "share_perm_rw";
352 pub const SHARE_PERM_RO: &str = "share_perm_ro";
353 pub const SHARE_TITLE: &str = "share_title";
354 pub const SHARE_DIALOG_HINT: &str = "share_dialog_hint";
355 pub const EXPIRES: &str = "expires";
356 pub const NEVER: &str = "never";
357 pub const CUSTOM: &str = "custom";
358 pub const UNTIL: &str = "until";
359 pub const EXPIRES_AT: &str = "expires_at";
360 pub const DELETE_PARTIAL: &str = "delete_partial";
361 pub const NEVER_EXPIRES: &str = "never_expires";
362 pub const ALLOW_EDITING: &str = "allow_editing";
363 pub const RW_DISABLED: &str = "rw_disabled";
364 pub const RW_RO_FOLDER: &str = "rw_ro_folder";
365 pub const CREATING: &str = "creating";
366 pub const CREATE_LINK: &str = "create_link";
367 pub const YOUR_SHARES: &str = "your_shares";
368 pub const DELETED_N_SHARES: &str = "deleted_n_shares";
369 pub const SHARES_DELETE_ERR: &str = "shares_delete_err";
370 pub const DELETE_ALL: &str = "delete_all";
371 pub const NO_SHARES: &str = "no_shares";
372 pub const SHARES_VIEW_HINT: &str = "shares_view_hint";
373 pub const SHARES_EMPTY_HINT: &str = "shares_empty_hint";
374 pub const COPY_LINK: &str = "copy_link";
375 pub const DELETE_SHARE: &str = "delete_share";
376 pub const SHARE_DELETED: &str = "share_deleted";
377 pub const SHARE_DELETE_ERR: &str = "share_delete_err";
378 pub const DELETE_ALL_TITLE: &str = "delete_all_title";
379 pub const DELETE_ALL_MSG: &str = "delete_all_msg";
380
381 // Login / setup
382 pub const LOGIN_ERROR: &str = "login_error";
383 pub const LOGIN_SUBTITLE: &str = "login_subtitle";
384 pub const SIGN_IN: &str = "sign_in";
385 pub const SETUP_NAME_ERR: &str = "setup_name_err";
386 pub const SETUP_PW_MISMATCH: &str = "setup_pw_mismatch";
387 pub const REPEAT_PASSWORD: &str = "repeat_password";
388 pub const SETUP_INTRO: &str = "setup_intro";
389 pub const CREATE_ADMIN: &str = "create_admin";
390
391 // File view / editor
392 pub const SAVE: &str = "save";
393 pub const CLOSE_ESC: &str = "close_esc";
394 pub const NO_PREVIEW: &str = "no_preview";
395 pub const NO_PREVIEW_MSG: &str = "no_preview_msg";
396 pub const DISMISS: &str = "dismiss";
397 pub const DISMISS_ERROR: &str = "dismiss_error";
398 pub const FILE_CHANGED: &str = "file_changed";
399 pub const MODIFIED_AFTER: &str = "modified_after";
400 pub const LOAD_LATEST: &str = "load_latest";
401 pub const KEEP_EDITING: &str = "keep_editing";
402 pub const DISCARD: &str = "discard";
403 pub const UNSAVED_CHANGES: &str = "unsaved_changes";
404 pub const DISCARD_QUESTION: &str = "discard_question";
405
406 #[allow(dead_code)]
407 // Server error codes (see server/src for the constructors)
408 pub const ERR_INTERNAL: &str = "err_internal";
409 pub const ERR_NOT_SIGNED_IN: &str = "err_not_signed_in";
410 pub const ERR_SESSION_EXPIRED: &str = "err_session_expired";
411 pub const ERR_INVALID_CREDENTIALS: &str = "err_invalid_credentials";
412 pub const ERR_ALREADY_SET_UP: &str = "err_already_set_up";
413 pub const ERR_SHARE_NOT_FOUND: &str = "err_share_not_found";
414 pub const ERR_SHARE_EXPIRED: &str = "err_share_expired";
415 pub const ERR_SHARE_TOKEN_FORBIDDEN: &str = "err_share_token_forbidden";
416 pub const ERR_NAME_LENGTH: &str = "err_name_length";
417 pub const ERR_PASSWORD_SHORT: &str = "err_password_short";
418 pub const ERR_ADMIN_ONLY: &str = "err_admin_only";
419 pub const ERR_USER_EXISTS: &str = "err_user_exists";
420 pub const ERR_USER_NOT_FOUND: &str = "err_user_not_found";
421 pub const ERR_OWN_ADMIN: &str = "err_own_admin";
422 pub const ERR_OWN_ACCOUNT: &str = "err_own_account";
423 pub const ERR_LAST_ADMIN: &str = "err_last_admin";
424 pub const ERR_OWN_DELETE: &str = "err_own_delete";
425 pub const ERR_LAST_ADMIN_DELETE: &str = "err_last_admin_delete";
426 pub const ERR_BAD_FORMAT: &str = "err_bad_format";
427 pub const ERR_NOT_A_FILE: &str = "err_not_a_file";
428 pub const ERR_TOO_LARGE_PREVIEW: &str = "err_too_large_preview";
429 pub const ERR_BAD_ACTION: &str = "err_bad_action";
430 pub const ERR_TOO_LARGE_SAVE: &str = "err_too_large_save";
431 pub const ERR_BAD_BODY: &str = "err_bad_body";
432 pub const ERR_INVALID_LANGUAGE: &str = "err_invalid_language";
433 pub const ERR_BAD_POST: &str = "err_bad_post";
434 pub const ERR_FOLDER_NAME_REQUIRED: &str = "err_folder_name_required";
435 pub const ERR_FILE_NAME_REQUIRED: &str = "err_file_name_required";
436 pub const ERR_NEW_NAME_REQUIRED: &str = "err_new_name_required";
437 pub const ERR_DST_REQUIRED: &str = "err_dst_required";
438 pub const ERR_PATH_REQUIRED: &str = "err_path_required";
439 pub const ERR_BAD_MULTIPART: &str = "err_bad_multipart";
440 pub const ERR_BAD_UPLOAD: &str = "err_bad_upload";
441 pub const ERR_PART_NO_NAME: &str = "err_part_no_name";
442 pub const ERR_BAD_PART_NAME: &str = "err_bad_part_name";
443 pub const ERR_FOLDER_EXISTS: &str = "err_folder_exists";
444 pub const ERR_SAVE_FAILED: &str = "err_save_failed";
445 pub const ERR_NO_FILES_UPLOADED: &str = "err_no_files_uploaded";
446 pub const ERR_FILES_EXIST: &str = "err_files_exist";
447 pub const ERR_BAD_UPLOAD_PATH: &str = "err_bad_upload_path";
448 pub const ERR_NO_SUCH_FOLDER: &str = "err_no_such_folder";
449 pub const ERR_READ_ONLY_FOLDER: &str = "err_read_only_folder";
450 pub const ERR_RW_SHARES_DISABLED: &str = "err_rw_shares_disabled";
451 pub const ERR_RW_RO_FOLDER: &str = "err_rw_ro_folder";
452 pub const ERR_FS_NOT_FOUND: &str = "err_fs_not_found";
453 pub const ERR_FS_NOT_A_DIR: &str = "err_fs_not_a_dir";
454 pub const ERR_FS_FORBIDDEN: &str = "err_fs_forbidden";
455 pub const ERR_FS_ROOT_MISSING: &str = "err_fs_root_missing";
456 pub const ERR_FS_CONFLICT: &str = "err_fs_conflict";
457 pub const ERR_SEARCH_FORBIDDEN: &str = "err_search_forbidden";
458 pub const ERR_SEARCH_BAD_SCOPE: &str = "err_search_bad_scope";
459 pub const ERR_SEARCH_BAD_ROOTS: &str = "err_search_bad_roots";
460 pub const ERR_ROOT_FORBIDDEN: &str = "err_root_forbidden";
461}
462
463/// Canonical English strings (key → text). The source of truth for which
464/// keys exist; DE and FR may be partial and fall back to English.
465const EN: &[(&str, &str)] = &[
466 ("no_folders_title", "No folders available"),
467 (
468 "no_folders_msg",
469 "An administrator hasn't assigned you any folders yet.",
470 ),
471 ("select_folder_title", "Select a folder"),
472 (
473 "select_folder_msg",
474 "Choose a folder from the sidebar to start browsing.",
475 ),
476 ("folder_gone_title", "Folder not available"),
477 (
478 "folder_gone_msg",
479 "This folder is no longer accessible. Choose another one from the sidebar.",
480 ),
481 ("loading", "Loading…"),
482 ("loading_share", "Loading share…"),
483 ("retry", "Retry"),
484 ("empty_folder", "This folder is empty."),
485 ("refresh", "Refresh"),
486 ("grid_view", "Grid view"),
487 ("list_view", "List view"),
488 ("open", "Open"),
489 ("open_in_new_tab", "Open in new tab"),
490 ("download", "Download"),
491 ("rename", "Rename"),
492 ("move", "Move"),
493 ("copy", "Copy"),
494 ("done", "Done"),
495 ("share", "Share"),
496 ("info", "Info"),
497 ("delete", "Delete"),
498 ("new_folder", "New folder"),
499 ("new_file", "New file"),
500 ("upload_files", "Upload files"),
501 ("upload_folder", "Upload folder"),
502 ("read_only_hint", "Read-only folder"),
503 ("read_only_badge", "Read-only"),
504 ("more_actions", "More actions"),
505 ("folder_name", "Folder name"),
506 ("file_name", "File name"),
507 ("create", "Create"),
508 ("rename_title", "Rename “{}”"),
509 ("new_name", "New name"),
510 (
511 "delete_folder_msg",
512 "Delete folder “{}” and everything inside it?\nThis cannot be undone.",
513 ),
514 ("delete_msg", "Delete “{}”?\nThis cannot be undone."),
515 ("cancel", "Cancel"),
516 (
517 "overwrite_question",
518 "The following files already exist. Overwrite them?",
519 ),
520 ("overwrite", "Overwrite"),
521 ("and_more", "…and {} more"),
522 (
523 "no_subfolders",
524 "No subfolders — the file will be placed in the current folder.",
525 ),
526 ("move_to", "Move “{}” to…"),
527 ("copy_to", "Copy “{}” to…"),
528 ("read_only_root", "{} (read-only)"),
529 ("move_here", "Move here"),
530 ("copy_here", "Copy here"),
531 ("top", "top"),
532 ("download_title", "Download “{}”"),
533 (
534 "download_formats_msg",
535 "Choose an archive format for the folder.",
536 ),
537 ("fmt_zip", "Zip"),
538 ("fmt_tar", "Tar"),
539 ("fmt_targz", "Tar + gzip"),
540 ("fmt_tarzst", "Tar + zstd"),
541 ("file_info", "File info"),
542 ("name", "Name"),
543 ("type", "Type"),
544 ("folder", "Folder"),
545 ("file", "File"),
546 ("size", "Size"),
547 ("modified", "Modified"),
548 ("location", "Location"),
549 ("close", "Close"),
550 ("folder_created", "Folder created"),
551 ("file_created", "File created"),
552 ("name_taken", "Name already in use"),
553 ("renamed", "Renamed"),
554 ("deleted", "Deleted"),
555 ("uploading", "Uploading {} files…"),
556 ("some_files_exist", "Some files already exist"),
557 ("upload_complete", "Upload complete"),
558 ("moved", "Moved"),
559 ("copied", "Copied"),
560 ("download_started", "Download started"),
561 ("saved", "Saved"),
562 ("menu", "Menu"),
563 ("open_menu", "Open menu"),
564 ("refresh_folders", "Refresh your folders"),
565 ("theme_title", "Theme: {} (click to switch)"),
566 ("log_out", "Log out"),
567 ("files", "Files"),
568 ("manage", "Manage"),
569 ("shares", "Shares"),
570 ("users", "Users"),
571 ("no_users", "No users."),
572 ("settings", "Settings"),
573 ("theme_auto", "Auto"),
574 ("theme_light", "Light"),
575 ("theme_dark", "Dark"),
576 ("search", "Search"),
577 ("search_placeholder", "Search files and their contents"),
578 ("search_name", "Name"),
579 ("search_content", "Content"),
580 ("search_both", "Both"),
581 ("search_goto", "Show in folder"),
582 ("search_run", "Search"),
583 ("search_stop", "Stop"),
584 ("searching", "searching…"),
585 ("search_n_results", "{} results"),
586 ("search_took", "{} ms"),
587 ("search_stopped", "stopped · {} results"),
588 ("search_scanned", "scanned {} files"),
589 ("search_skipped", "{} skipped (over 10 MiB)"),
590 ("search_n_matches", "{} matches"),
591 ("search_in_n_files", "in {} files"),
592 ("search_collapse_all", "Collapse all"),
593 ("search_expand_all", "Expand all"),
594 ("search_shown_of", "Showing {} of {}"),
595 ("search_show_more", "Show {} more"),
596 ("search_no_results", "no results"),
597 ("saving", "Saving…"),
598 ("profile", "Profile"),
599 ("server", "Server"),
600 ("profile_saved", "Profile saved"),
601 ("profile_save_err", "Could not save profile: {}"),
602 ("settings_saved", "Settings saved"),
603 ("settings_save_err", "Could not save settings: {}"),
604 ("single_click_label", "Use single click to open"),
605 (
606 "single_click_desc",
607 "When on, a single click opens an entry and Ctrl+click selects it. When off, a single click selects and a double click opens.",
608 ),
609 ("save_profile", "Save profile"),
610 ("allow_rw_shares", "Allow writable shares"),
611 (
612 "allow_rw_shares_desc",
613 "Let users create read-write share links. Off by default.",
614 ),
615 ("save_settings", "Save settings"),
616 ("language", "Language"),
617 (
618 "language_desc",
619 "The interface language. Automatic uses your browser's language.",
620 ),
621 ("lang_auto", "Automatic"),
622 ("lang_en", "English"),
623 ("lang_de", "German"),
624 ("lang_fr", "French"),
625 (
626 "delete_user_confirm",
627 "Delete user “{}”? This cannot be undone.",
628 ),
629 ("user_delete_err", "Could not delete user"),
630 ("new_user", "New user"),
631 ("edit", "Edit"),
632 ("delete_user", "Delete user"),
633 ("cant_delete_self", "You cannot delete yourself"),
634 ("root_dup_err", "That folder is already in the list."),
635 ("name_required", "A name is required."),
636 ("pw_short", "Password must be at least 8 characters."),
637 ("need_folder", "Give the user at least one folder."),
638 ("user_created", "User created"),
639 ("user_updated", "User updated"),
640 ("user_save_err", "Could not save user: {}"),
641 ("password", "Password"),
642 ("new_password_keep", "New password (leave blank to keep)"),
643 ("administrator", "Administrator"),
644 ("active_can_signin", "Active (can sign in)"),
645 ("remove_folder", "Remove folder"),
646 ("create_user", "Create user"),
647 ("save", "Save"),
648 ("edit_user_title", "Edit “{}”"),
649 ("folders", "Folders"),
650 (
651 "folders_paths_hint",
652 "Paths are relative to the server root. “.” is the whole root.",
653 ),
654 ("add", "Add"),
655 ("mode_rw", "read-write"),
656 ("expiry_1h", "1 hour"),
657 ("expiry_1d", "1 day"),
658 ("expiry_1w", "1 week"),
659 ("expiry_1mo", "1 month"),
660 ("meta_expires", "expires {}"),
661 ("meta_created", "created {}"),
662 ("mode_ro", "read-only"),
663 ("share_not_found", "Share not found"),
664 (
665 "share_not_found_msg",
666 "This link is invalid or the share was removed.",
667 ),
668 ("share_expired", "Share expired"),
669 ("share_expired_msg", "This share link is no longer active."),
670 ("copy_manual", "Copy the link manually"),
671 ("link_copied", "Link copied to clipboard"),
672 ("share_create_err", "Could not create share: {}"),
673 ("share_link", "Share link"),
674 (
675 "share_perm_rw",
676 "Anyone with this link can read and write “{}”.",
677 ),
678 ("share_perm_ro", "Anyone with this link can read “{}”."),
679 ("share_title", "Share {}"),
680 (
681 "share_dialog_hint",
682 "Create a link for this item. Writable links let viewers upload and edit.",
683 ),
684 ("expires", "Expires"),
685 ("never", "Never"),
686 ("custom", "Custom…"),
687 ("until", "Until"),
688 ("expires_at", "Expires {}"),
689 ("delete_partial", "{} of {} shares could not be deleted"),
690 ("never_expires", "Never expires"),
691 ("allow_editing", "Allow editing (read-write)"),
692 ("rw_disabled", "Writable shares are disabled by the server."),
693 (
694 "rw_ro_folder",
695 "This folder is read-only, so the share is read-only too.",
696 ),
697 ("creating", "Creating…"),
698 ("create_link", "Create link"),
699 ("your_shares", "Your shares"),
700 ("deleted_n_shares", "Deleted {} shares"),
701 ("shares_delete_err", "Could not delete shares"),
702 ("delete_all", "Delete all"),
703 ("no_shares", "No shares yet"),
704 (
705 "shares_view_hint",
706 "Links you created. Right-click a file or folder and choose “Share” to add one.",
707 ),
708 (
709 "shares_empty_hint",
710 "Right-click a file or folder and choose “Share” to create a link.",
711 ),
712 ("copy_link", "Copy link"),
713 ("delete_share", "Delete share"),
714 ("share_deleted", "Share deleted"),
715 ("share_delete_err", "Could not delete share"),
716 ("delete_all_title", "Delete all shares?"),
717 (
718 "delete_all_msg",
719 "Delete all {} share links? Anyone holding them loses access. This cannot be undone.",
720 ),
721 ("login_subtitle", "Sign in to continue."),
722 ("login_error", "Please enter your name and password."),
723 ("sign_in", "Sign in"),
724 ("setup_name_err", "Please choose a name (1–64 characters)."),
725 ("setup_pw_mismatch", "Passwords don't match."),
726 ("repeat_password", "Repeat password"),
727 ("create_admin", "Create admin account"),
728 (
729 "setup_intro",
730 "First boot: create the admin account. This user can manage other users and server settings.",
731 ),
732 ("close_esc", "Close (Esc)"),
733 ("no_preview", "No preview available"),
734 ("no_preview_msg", "The app can’t display “{}” here."),
735 ("dismiss", "Dismiss"),
736 ("dismiss_error", "Dismiss error"),
737 ("file_changed", "File changed on disk"),
738 (
739 "modified_after",
740 "This file was modified after you opened it. “Load latest” discards your unsaved changes; “Overwrite” keeps your version.",
741 ),
742 ("load_latest", "Load latest"),
743 ("keep_editing", "Keep editing"),
744 ("discard", "Discard"),
745 ("unsaved_changes", "Unsaved changes"),
746 (
747 "discard_question",
748 "Discard your changes and close the editor?",
749 ),
750 (k::ERR_INTERNAL, "internal error"),
751 (k::ERR_NOT_SIGNED_IN, "not signed in"),
752 (
753 k::ERR_SESSION_EXPIRED,
754 "session expired, please sign in again",
755 ),
756 (k::ERR_INVALID_CREDENTIALS, "invalid name or password"),
757 (k::ERR_ALREADY_SET_UP, "server is already set up"),
758 (k::ERR_SHARE_NOT_FOUND, "share not found"),
759 (k::ERR_SHARE_EXPIRED, "this share has expired"),
760 (
761 k::ERR_SHARE_TOKEN_FORBIDDEN,
762 "a share token cannot be used here; sign in instead",
763 ),
764 (k::ERR_NAME_LENGTH, "name must be 1–64 characters"),
765 (
766 k::ERR_PASSWORD_SHORT,
767 "password must be at least 8 characters",
768 ),
769 (k::ERR_ADMIN_ONLY, "admin only"),
770 (k::ERR_USER_EXISTS, "a user with that name already exists"),
771 (k::ERR_USER_NOT_FOUND, "user not found"),
772 (k::ERR_OWN_ADMIN, "you cannot remove your own admin rights"),
773 (k::ERR_OWN_ACCOUNT, "you cannot disable your own account"),
774 (k::ERR_LAST_ADMIN, "cannot remove the last active admin"),
775 (k::ERR_OWN_DELETE, "you cannot delete your own account"),
776 (
777 k::ERR_LAST_ADMIN_DELETE,
778 "cannot delete the last active admin",
779 ),
780 (
781 k::ERR_BAD_FORMAT,
782 "format must be one of: zip, tar, tar.gz, tar.zst",
783 ),
784 (k::ERR_NOT_A_FILE, "not a file"),
785 (k::ERR_TOO_LARGE_PREVIEW, "file too large to preview"),
786 (k::ERR_BAD_ACTION, "expected action=content"),
787 (k::ERR_TOO_LARGE_SAVE, "file too large to save"),
788 (k::ERR_BAD_BODY, "invalid request body"),
789 (k::ERR_INVALID_LANGUAGE, "invalid language tag"),
790 (
791 k::ERR_BAD_POST,
792 "POST expects a multipart upload, a JSON mutation, or ?action=mkdir/create-file",
793 ),
794 (k::ERR_FOLDER_NAME_REQUIRED, "a folder name is required"),
795 (k::ERR_FILE_NAME_REQUIRED, "a file name is required"),
796 (k::ERR_NEW_NAME_REQUIRED, "new_name is required"),
797 (k::ERR_DST_REQUIRED, "dst_root_id is required"),
798 (k::ERR_PATH_REQUIRED, "a path inside the folder is required"),
799 (
800 k::ERR_BAD_MULTIPART,
801 "expected multipart/form-data with a boundary",
802 ),
803 (k::ERR_BAD_UPLOAD, "invalid upload data"),
804 (k::ERR_PART_NO_NAME, "part without a name"),
805 (k::ERR_BAD_PART_NAME, "invalid part name"),
806 (
807 k::ERR_FOLDER_EXISTS,
808 "a folder with this name already exists",
809 ),
810 (k::ERR_SAVE_FAILED, "could not save the file"),
811 (k::ERR_NO_FILES_UPLOADED, "no files were uploaded"),
812 (k::ERR_FILES_EXIST, "some files already exist"),
813 (k::ERR_BAD_UPLOAD_PATH, "invalid file path in upload"),
814 (k::ERR_NO_SUCH_FOLDER, "no such folder"),
815 (k::ERR_READ_ONLY_FOLDER, "read-only folder"),
816 (k::ERR_RW_SHARES_DISABLED, "writable shares are disabled"),
817 (
818 k::ERR_RW_RO_FOLDER,
819 "this folder is read-only for you, so it cannot be shared writably",
820 ),
821 (k::ERR_FS_NOT_FOUND, "folder not found"),
822 (k::ERR_FS_NOT_A_DIR, "not a folder"),
823 (k::ERR_FS_FORBIDDEN, "access denied"),
824 (
825 k::ERR_FS_ROOT_MISSING,
826 "the configured folder no longer exists",
827 ),
828 (k::ERR_FS_CONFLICT, "already exists"),
829 (
830 k::ERR_SEARCH_FORBIDDEN,
831 "search requires a signed-in session",
832 ),
833 (
834 k::ERR_SEARCH_BAD_SCOPE,
835 "scope must be name, content or both",
836 ),
837 (k::ERR_SEARCH_BAD_ROOTS, "root ids must be numbers"),
838 (k::ERR_ROOT_FORBIDDEN, "root not accessible"),
839];
840
841/// German translations.
842const DE: &[(&str, &str)] = &[
843 ("no_folders_title", "Keine Ordner verfügbar"),
844 (
845 "no_folders_msg",
846 "Ein Administrator hat Ihnen noch keine Ordner zugewiesen.",
847 ),
848 ("select_folder_title", "Ordner auswählen"),
849 (
850 "select_folder_msg",
851 "Wählen Sie links einen Ordner aus, um mit dem Durchsuchen zu beginnen.",
852 ),
853 ("folder_gone_title", "Ordner nicht verfügbar"),
854 (
855 "folder_gone_msg",
856 "Dieser Ordner ist nicht mehr verfügbar. Wählen Sie links einen anderen aus.",
857 ),
858 ("loading", "Lade…"),
859 ("loading_share", "Freigabe wird geladen…"),
860 ("retry", "Erneut versuchen"),
861 ("empty_folder", "Dieser Ordner ist leer."),
862 ("refresh", "Aktualisieren"),
863 ("grid_view", "Kachelansicht"),
864 ("list_view", "Liste"),
865 ("open", "Öffnen"),
866 ("open_in_new_tab", "In neuem Tab öffnen"),
867 ("download", "Herunterladen"),
868 ("rename", "Umbenennen"),
869 ("move", "Verschieben"),
870 ("copy", "Kopieren"),
871 ("done", "Fertig"),
872 ("share", "Teilen"),
873 ("info", "Info"),
874 ("delete", "Löschen"),
875 ("new_folder", "Neuer Ordner"),
876 ("new_file", "Neue Datei"),
877 ("upload_files", "Dateien hochladen"),
878 ("upload_folder", "Ordner hochladen"),
879 ("read_only_hint", "Schreibgeschützter Ordner"),
880 ("read_only_badge", "Schreibgeschützt"),
881 ("more_actions", "Weitere Aktionen"),
882 ("folder_name", "Ordnername"),
883 ("file_name", "Dateiname"),
884 ("create", "Erstellen"),
885 ("rename_title", "„{}“ umbenennen"),
886 ("new_name", "Neuer Name"),
887 (
888 "delete_folder_msg",
889 "Ordner „{}“ mit allem Inhalt löschen?\nDas kann nicht rückgängig gemacht werden.",
890 ),
891 (
892 "delete_msg",
893 "Lose „{}“ löschen?\nDas kann nicht rückgängig gemacht werden.",
894 ),
895 ("cancel", "Abbrechen"),
896 (
897 "overwrite_question",
898 "Die folgenden Dateien existieren bereits. Überschreiben?",
899 ),
900 ("overwrite", "Überschreiben"),
901 ("and_more", "…und {} weitere"),
902 (
903 "no_subfolders",
904 "Keine Unterordner – die Datei wird im aktuellen Ordner abgelegt.",
905 ),
906 ("move_to", "„{}“ verschieben nach…"),
907 ("copy_to", "„{}“ kopieren nach…"),
908 ("read_only_root", "{} (nur lesen)"),
909 ("move_here", "Hierher verschieben"),
910 ("copy_here", "Hierher kopieren"),
911 ("top", "oben"),
912 ("download_title", "„{}“ herunterladen"),
913 (
914 "download_formats_msg",
915 "Archivformat für den Ordner wählen.",
916 ),
917 ("fmt_zip", "Zip"),
918 ("fmt_tar", "Tar"),
919 ("fmt_targz", "Tar + gzip"),
920 ("fmt_tarzst", "Tar + zstd"),
921 ("file_info", "Datei-Info"),
922 ("name", "Name"),
923 ("type", "Typ"),
924 ("folder", "Ordner"),
925 ("file", "Datei"),
926 ("size", "Größe"),
927 ("modified", "Geändert"),
928 ("location", "Ort"),
929 ("close", "Schließen"),
930 ("folder_created", "Ordner erstellt"),
931 ("file_created", "Datei erstellt"),
932 ("name_taken", "Name wird bereits verwendet"),
933 ("renamed", "Umbenannt"),
934 ("deleted", "Gelöscht"),
935 ("uploading", "{} Datei(en) werden hochgeladen…"),
936 ("some_files_exist", "Einige Dateien existieren bereits"),
937 ("upload_complete", "Hochladen abgeschlossen"),
938 ("moved", "Verschoben"),
939 ("copied", "Kopiert"),
940 ("download_started", "Download gestartet"),
941 ("saved", "Gespeichert"),
942 ("menu", "Menü"),
943 ("open_menu", "Menü öffnen"),
944 ("refresh_folders", "Ordner aktualisieren"),
945 ("theme_title", "Design: {} (klicken zum Wechseln)"),
946 ("log_out", "Abmelden"),
947 ("files", "Dateien"),
948 ("manage", "Verwalten"),
949 ("shares", "Teilen"),
950 ("users", "Benutzer"),
951 ("no_users", "Keine Benutzer."),
952 ("settings", "Einstellungen"),
953 ("theme_auto", "Auto"),
954 ("theme_light", "Hell"),
955 ("theme_dark", "Dunkel"),
956 ("search", "Suche"),
957 (
958 "search_placeholder",
959 "Dateien und deren Inhalte durchsuchen",
960 ),
961 ("search_name", "Name"),
962 ("search_content", "Inhalt"),
963 ("search_both", "Beides"),
964 ("search_goto", "Im Ordner anzeigen"),
965 ("search_run", "Suchen"),
966 ("search_stop", "Stopp"),
967 ("searching", "Suche läuft…"),
968 ("search_n_results", "{} Treffer"),
969 ("search_took", "{} ms"),
970 ("search_stopped", "gestoppt · {} Treffer"),
971 ("search_scanned", "{} Dateien durchsucht"),
972 ("search_skipped", "{} übersprungen (über 10 MiB)"),
973 ("search_n_matches", "{} Treffer"),
974 ("search_in_n_files", "in {} Dateien"),
975 ("search_collapse_all", "Alle einklappen"),
976 ("search_expand_all", "Alle ausklappen"),
977 ("search_shown_of", "{} von {} angezeigt"),
978 ("search_show_more", "{} weitere anzeigen"),
979 ("search_no_results", "keine Treffer"),
980 ("saving", "Wird gespeichert…"),
981 ("profile", "Profil"),
982 ("server", "Server"),
983 ("profile_saved", "Profil gespeichert"),
984 (
985 "profile_save_err",
986 "Profil konnte nicht gespeichert werden: {}",
987 ),
988 ("settings_saved", "Einstellungen gespeichert"),
989 (
990 "settings_save_err",
991 "Einstellungen konnten nicht gespeichert werden: {}",
992 ),
993 ("single_click_label", "Einzelklick öffnet Einträge"),
994 (
995 "single_click_desc",
996 "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.",
997 ),
998 ("save_profile", "Profil speichern"),
999 ("allow_rw_shares", "Schreibbare Links erlauben"),
1000 (
1001 "allow_rw_shares_desc",
1002 "Benutzern erlauben, schreibbare Freigabelinks zu erstellen. Standardmäßig aus.",
1003 ),
1004 ("save_settings", "Einstellungen speichern"),
1005 ("language", "Sprache"),
1006 (
1007 "language_desc",
1008 "Die Sprache der Benutzeroberfläche. „Automatisch“ verwendet die Sprache Ihres Browsers.",
1009 ),
1010 ("lang_auto", "Automatisch"),
1011 ("lang_en", "Englisch"),
1012 ("lang_de", "Deutsch"),
1013 ("lang_fr", "Französisch"),
1014 (
1015 "delete_user_confirm",
1016 "Benutzer „{}“ löschen? Das kann nicht rückgängig gemacht werden.",
1017 ),
1018 ("user_delete_err", "Benutzer konnte nicht gelöscht werden"),
1019 ("new_user", "Neuer Benutzer"),
1020 ("edit", "Bearbeiten"),
1021 ("delete_user", "Benutzer löschen"),
1022 ("cant_delete_self", "Sie können sich nicht selbst löschen"),
1023 ("root_dup_err", "Dieser Ordner ist bereits in der Liste."),
1024 ("name_required", "Ein Name ist erforderlich."),
1025 (
1026 "pw_short",
1027 "Das Passwort muss mindestens 8 Zeichen lang sein.",
1028 ),
1029 (
1030 "need_folder",
1031 "Dem Benutzer muss mindestens ein Ordner zugewiesen werden.",
1032 ),
1033 ("user_created", "Benutzer erstellt"),
1034 ("user_updated", "Benutzer aktualisiert"),
1035 (
1036 "user_save_err",
1037 "Benutzer konnte nicht gespeichert werden: {}",
1038 ),
1039 ("password", "Passwort"),
1040 (
1041 "new_password_keep",
1042 "Neues Passwort (leer lassen, um beizubehalten)",
1043 ),
1044 ("administrator", "Administrator"),
1045 ("active_can_signin", "Aktiv (kann sich anmelden)"),
1046 ("remove_folder", "Ordner entfernen"),
1047 ("create_user", "Benutzer erstellen"),
1048 ("save", "Speichern"),
1049 ("edit_user_title", "„{}“ bearbeiten"),
1050 ("folders", "Ordner"),
1051 (
1052 "folders_paths_hint",
1053 "Pfade sind relativ zur Serverwurzel. „.“ ist der gesamte Wurzelordner.",
1054 ),
1055 ("add", "Hinzufügen"),
1056 ("mode_rw", "lesen/schreiben"),
1057 ("expiry_1h", "1 Stunde"),
1058 ("expiry_1d", "1 Tag"),
1059 ("expiry_1w", "1 Woche"),
1060 ("expiry_1mo", "1 Monat"),
1061 ("meta_expires", "läuft ab am {}"),
1062 ("meta_created", "erstellt am {}"),
1063 ("mode_ro", "nur lesen"),
1064 ("share_not_found", "Link nicht gefunden"),
1065 (
1066 "share_not_found_msg",
1067 "Dieser Link ist ungültig oder die Freigabe wurde entfernt.",
1068 ),
1069 ("share_expired", "Link abgelaufen"),
1070 ("share_expired_msg", "Dieser Link ist nicht mehr aktiv."),
1071 ("copy_manual", "Link manuell kopieren"),
1072 ("link_copied", "Link in die Zwischenablage kopiert"),
1073 ("share_create_err", "Link konnte nicht erstellt werden: {}"),
1074 ("share_link", "Teil-Link"),
1075 (
1076 "share_perm_rw",
1077 "Jeder mit diesem Link kann „{}“ lesen und schreiben.",
1078 ),
1079 ("share_perm_ro", "Jeder mit diesem Link kann „{}“ lesen."),
1080 ("share_title", "{} teilen"),
1081 (
1082 "share_dialog_hint",
1083 "Erstellt einen Link für diesen Eintrag. Mit schreibbaren Links können Empfänger Dateien hochladen und bearbeiten.",
1084 ),
1085 ("expires", "Läuft ab"),
1086 ("never", "Nie"),
1087 ("custom", "Eigentlich…"),
1088 ("until", "Bis"),
1089 ("expires_at", "Läuft {} ab"),
1090 (
1091 "delete_partial",
1092 "{} von {} Links konnten nicht gelöscht werden",
1093 ),
1094 ("never_expires", "Läuft nie ab"),
1095 ("allow_editing", "Bearbeitung erlauben (lesen/schreiben)"),
1096 (
1097 "rw_disabled",
1098 "Schreibbare Freigaben sind vom Server deaktiviert.",
1099 ),
1100 (
1101 "rw_ro_folder",
1102 "Dieser Ordner ist schreibgeschützt, daher ist die Freigabe auch schreibgeschützt.",
1103 ),
1104 ("creating", "Wird erstellt…"),
1105 ("create_link", "Link erstellen"),
1106 ("your_shares", "Ihre Links"),
1107 ("deleted_n_shares", "{} Link(s) gelöscht"),
1108 ("shares_delete_err", "Links konnten nicht gelöscht werden"),
1109 ("delete_all", "Alle löschen"),
1110 ("no_shares", "Noch keine Links"),
1111 (
1112 "shares_view_hint",
1113 "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.",
1114 ),
1115 (
1116 "shares_empty_hint",
1117 "Klicken Sie mit der rechten Maustaste auf eine Datei oder einen Ordner und wählen Sie „Teilen“, um einen Link zu erstellen.",
1118 ),
1119 ("copy_link", "Link kopieren"),
1120 ("delete_share", "Link löschen"),
1121 ("share_deleted", "Link gelöscht"),
1122 ("share_delete_err", "Link konnte nicht gelöscht werden"),
1123 ("delete_all_title", "Alle Links löschen?"),
1124 (
1125 "delete_all_msg",
1126 "Alle {} Freigabelinks löschen? Jeder, der sie besitzt, verliert den Zugriff. Das kann nicht rückgängig gemacht werden.",
1127 ),
1128 ("login_subtitle", "Anmelden, um fortzufahren."),
1129 ("login_error", "Bitte Name und Passwort eingeben."),
1130 ("sign_in", "Anmelden"),
1131 (
1132 "setup_name_err",
1133 "Bitte wählen Sie einen Namen (1–64 Zeichen).",
1134 ),
1135 ("setup_pw_mismatch", "Die Passwörter stimmen nicht überein."),
1136 ("repeat_password", "Passwort wiederholen"),
1137 ("create_admin", "Administrator-Konto erstellen"),
1138 (
1139 "setup_intro",
1140 "Erster Start: Erstellen Sie das Administrator-Konto. Dieser Benutzer kann andere Benutzer und die Servereinstellungen verwalten.",
1141 ),
1142 ("close_esc", "Schließen (Esc)"),
1143 ("no_preview", "Keine Vorschau verfügbar"),
1144 ("no_preview_msg", "Die App kann „{}“ hier nicht anzeigen."),
1145 ("dismiss", "Schließen"),
1146 ("dismiss_error", "Fehler schließen"),
1147 ("file_changed", "Datei auf der Festplatte geändert"),
1148 (
1149 "modified_after",
1150 "Diese Datei wurde nach dem Öffnen geändert. „Neueste laden“ verwirft Ihre ungespeicherten Änderungen; „Überschreiben“ behält Ihre Version bei.",
1151 ),
1152 ("load_latest", "Neueste Version laden"),
1153 ("keep_editing", "Weiter bearbeiten"),
1154 ("discard", "Verwerfen"),
1155 ("unsaved_changes", "Ungespeicherte Änderungen"),
1156 (
1157 "discard_question",
1158 "Änderungen verwerfen und den Editor schließen?",
1159 ),
1160 (k::ERR_INTERNAL, "interner Fehler"),
1161 (k::ERR_NOT_SIGNED_IN, "nicht angemeldet"),
1162 (
1163 k::ERR_SESSION_EXPIRED,
1164 "Sitzung abgelaufen, bitte erneut anmelden",
1165 ),
1166 (k::ERR_INVALID_CREDENTIALS, "Name oder Passwort ungültig"),
1167 (k::ERR_ALREADY_SET_UP, "Server ist bereits eingerichtet"),
1168 (k::ERR_SHARE_NOT_FOUND, "Link nicht gefunden"),
1169 (k::ERR_SHARE_EXPIRED, "dieser Link ist abgelaufen"),
1170 (
1171 k::ERR_SHARE_TOKEN_FORBIDDEN,
1172 "ein Freigabetoken kann hier nicht verwendet werden; bitte anmelden",
1173 ),
1174 (k::ERR_NAME_LENGTH, "der Name muss 1–64 Zeichen haben"),
1175 (
1176 k::ERR_PASSWORD_SHORT,
1177 "das Passwort muss mindestens 8 Zeichen lang sein",
1178 ),
1179 (k::ERR_ADMIN_ONLY, "nur für Administratoren"),
1180 (
1181 k::ERR_USER_EXISTS,
1182 "ein Benutzer mit diesem Namen existiert bereits",
1183 ),
1184 (k::ERR_USER_NOT_FOUND, "Benutzer nicht gefunden"),
1185 (
1186 k::ERR_OWN_ADMIN,
1187 "Sie können Ihre eigenen Administratorrechte nicht entfernen",
1188 ),
1189 (
1190 k::ERR_OWN_ACCOUNT,
1191 "Sie können Ihr eigenes Konto nicht deaktivieren",
1192 ),
1193 (
1194 k::ERR_LAST_ADMIN,
1195 "der letzte aktive Administrator kann nicht entfernt werden",
1196 ),
1197 (
1198 k::ERR_OWN_DELETE,
1199 "Sie können Ihr eigenes Konto nicht löschen",
1200 ),
1201 (
1202 k::ERR_LAST_ADMIN_DELETE,
1203 "der letzte aktive Administrator kann nicht gelöscht werden",
1204 ),
1205 (
1206 k::ERR_BAD_FORMAT,
1207 "das Format muss eines der folgenden sein: zip, tar, tar.gz, tar.zst",
1208 ),
1209 (k::ERR_NOT_A_FILE, "keine Datei"),
1210 (k::ERR_TOO_LARGE_PREVIEW, "Datei zu groß für eine Vorschau"),
1211 (k::ERR_BAD_ACTION, "action=content erwartet"),
1212 (k::ERR_TOO_LARGE_SAVE, "Datei zu groß zum Speichern"),
1213 (k::ERR_INVALID_LANGUAGE, "ungültiges Sprachkürzel"),
1214 (k::ERR_BAD_BODY, "ungültiger Anfrageinhalt"),
1215 (
1216 k::ERR_BAD_POST,
1217 "POST erwartet einen Multipart-Upload, eine JSON-Mutation oder ?action=mkdir/create-file",
1218 ),
1219 (
1220 k::ERR_FOLDER_NAME_REQUIRED,
1221 "ein Ordnernamen ist erforderlich",
1222 ),
1223 (k::ERR_FILE_NAME_REQUIRED, "ein Dateiname ist erforderlich"),
1224 (k::ERR_NEW_NAME_REQUIRED, "new_name ist erforderlich"),
1225 (k::ERR_DST_REQUIRED, "dst_root_id ist erforderlich"),
1226 (
1227 k::ERR_PATH_REQUIRED,
1228 "ein Pfad innerhalb des Ordners ist erforderlich",
1229 ),
1230 (
1231 k::ERR_BAD_MULTIPART,
1232 "multipart/form-data mit Boundary erwartet",
1233 ),
1234 (k::ERR_BAD_UPLOAD, "ungültige Upload-Daten"),
1235 (k::ERR_PART_NO_NAME, "Part ohne Namen"),
1236 (k::ERR_BAD_PART_NAME, "ungültiger Part-Name"),
1237 (
1238 k::ERR_FOLDER_EXISTS,
1239 "ein Ordner mit diesem Namen existiert bereits",
1240 ),
1241 (
1242 k::ERR_SAVE_FAILED,
1243 "die Datei konnte nicht gespeichert werden",
1244 ),
1245 (k::ERR_NO_FILES_UPLOADED, "keine Dateien hochgeladen"),
1246 (k::ERR_FILES_EXIST, "einige Dateien existieren bereits"),
1247 (k::ERR_BAD_UPLOAD_PATH, "ungültiger Dateipfad im Upload"),
1248 (k::ERR_NO_SUCH_FOLDER, "kein solcher Ordner"),
1249 (k::ERR_READ_ONLY_FOLDER, "schreibgeschützter Ordner"),
1250 (
1251 k::ERR_RW_SHARES_DISABLED,
1252 "schreibbare Freigaben sind deaktiviert",
1253 ),
1254 (
1255 k::ERR_RW_RO_FOLDER,
1256 "dieser Ordner ist für Sie schreibgeschützt und kann nicht schreibbar freigegeben werden",
1257 ),
1258 (k::ERR_FS_NOT_FOUND, "Ordner nicht gefunden"),
1259 (k::ERR_FS_NOT_A_DIR, "kein Ordner"),
1260 (k::ERR_FS_FORBIDDEN, "Zugriff verweigert"),
1261 (
1262 k::ERR_FS_ROOT_MISSING,
1263 "der konfigurierte Ordner existiert nicht mehr",
1264 ),
1265 (k::ERR_FS_CONFLICT, "existiert bereits"),
1266 (
1267 k::ERR_SEARCH_FORBIDDEN,
1268 "Suche erfordert eine angemeldete Sitzung",
1269 ),
1270 (
1271 k::ERR_SEARCH_BAD_SCOPE,
1272 "Bereich muss name, content oder both sein",
1273 ),
1274 (k::ERR_SEARCH_BAD_ROOTS, "Ordner-IDs müssen Zahlen sein"),
1275 (k::ERR_ROOT_FORBIDDEN, "Ordner nicht verfügbar"),
1276];
1277
1278/// French translations.
1279const FR: &[(&str, &str)] = &[
1280 ("no_folders_title", "Aucun dossier disponible"),
1281 (
1282 "no_folders_msg",
1283 "Un administrateur ne vous a pas encore affecté de dossiers.",
1284 ),
1285 ("select_folder_title", "Choisir un dossier"),
1286 (
1287 "select_folder_msg",
1288 "Choisissez un dossier dans le panneau latéral pour commencer à naviguer.",
1289 ),
1290 ("folder_gone_title", "Dossier indisponible"),
1291 (
1292 "folder_gone_msg",
1293 "Ce dossier n'est plus accessible. Choisissez-en un autre dans le panneau latéral.",
1294 ),
1295 ("loading", "Chargement…"),
1296 ("loading_share", "Chargement du partage…"),
1297 ("retry", "Réessayer"),
1298 ("empty_folder", "Ce dossier est vide."),
1299 ("refresh", "Actualiser"),
1300 ("grid_view", "Vue en grille"),
1301 ("list_view", "Vue en liste"),
1302 ("open", "Ouvrir"),
1303 ("open_in_new_tab", "Ouvrir dans un nouvel onglet"),
1304 ("download", "Télécharger"),
1305 ("rename", "Renommer"),
1306 ("move", "Déplacer"),
1307 ("copy", "Copier"),
1308 ("done", "Terminé"),
1309 ("share", "Partager"),
1310 ("info", "Infos"),
1311 ("delete", "Supprimer"),
1312 ("new_folder", "Nouveau dossier"),
1313 ("new_file", "Nouveau fichier"),
1314 ("upload_files", "Téléverser des fichiers"),
1315 ("upload_folder", "Téléverser un dossier"),
1316 ("read_only_hint", "Dossier en lecture seule"),
1317 ("read_only_badge", "Lecture seule"),
1318 ("more_actions", "Plus d'actions"),
1319 ("folder_name", "Nom du dossier"),
1320 ("file_name", "Nom du fichier"),
1321 ("create", "Créer"),
1322 ("rename_title", "Renommer « {} »"),
1323 ("new_name", "Nouveau nom"),
1324 (
1325 "delete_folder_msg",
1326 "Supprimer le dossier « {} » et tout son contenu ?\nCette action est irréversible.",
1327 ),
1328 (
1329 "delete_msg",
1330 "Supprimer « {} » ?\nCette action est irréversible.",
1331 ),
1332 ("cancel", "Annuler"),
1333 (
1334 "overwrite_question",
1335 "Les fichiers suivants existent déjà. Les écraser ?",
1336 ),
1337 ("overwrite", "Écraser"),
1338 ("and_more", "…et {} de plus"),
1339 (
1340 "no_subfolders",
1341 "Aucun sous-dossier – le fichier sera placé dans le dossier actuel.",
1342 ),
1343 ("move_to", "Déplacer « {} » vers…"),
1344 ("copy_to", "Copier « {} » vers…"),
1345 ("read_only_root", "{} (lecture seule)"),
1346 ("move_here", "Déplacer ici"),
1347 ("copy_here", "Copier ici"),
1348 ("top", "racine"),
1349 ("download_title", "Télécharger « {} »"),
1350 (
1351 "download_formats_msg",
1352 "Choisissez un format d'archivage pour le dossier.",
1353 ),
1354 ("fmt_zip", "Zip"),
1355 ("fmt_tar", "Tar"),
1356 ("fmt_targz", "Tar + gzip"),
1357 ("fmt_tarzst", "Tar + zstd"),
1358 ("file_info", "Informations"),
1359 ("name", "Nom"),
1360 ("type", "Type"),
1361 ("folder", "Dossier"),
1362 ("file", "Fichier"),
1363 ("size", "Taille"),
1364 ("modified", "Modifié"),
1365 ("location", "Emplacement"),
1366 ("close", "Fermer"),
1367 ("folder_created", "Dossier créé"),
1368 ("file_created", "Fichier créé"),
1369 ("name_taken", "Ce nom est déjà utilisé"),
1370 ("renamed", "Renommé"),
1371 ("deleted", "Supprimé"),
1372 ("uploading", "Téléversement de {} fichier(s)…"),
1373 ("some_files_exist", "Certains fichiers existent déjà"),
1374 ("upload_complete", "Téléversement terminé"),
1375 ("moved", "Déplacé"),
1376 ("copied", "Copié"),
1377 ("download_started", "Téléchargement démarré"),
1378 ("saved", "Enregistré"),
1379 ("menu", "Menu"),
1380 ("open_menu", "Ouvrir le menu"),
1381 ("refresh_folders", "Actualiser vos dossiers"),
1382 ("theme_title", "Thème : {} (cliquer pour changer)"),
1383 ("log_out", "Se déconnecter"),
1384 ("files", "Fichiers"),
1385 ("manage", "Gérer"),
1386 ("shares", "Partages"),
1387 ("users", "Utilisateurs"),
1388 ("no_users", "Aucun utilisateur."),
1389 ("settings", "Paramètres"),
1390 ("theme_auto", "Auto"),
1391 ("theme_light", "Clair"),
1392 ("theme_dark", "Sombre"),
1393 ("search", "Rechercher"),
1394 (
1395 "search_placeholder",
1396 "Rechercher dans les fichiers et leur contenu",
1397 ),
1398 ("search_name", "Nom"),
1399 ("search_content", "Contenu"),
1400 ("search_both", "Les deux"),
1401 ("search_goto", "Afficher dans le dossier"),
1402 ("search_run", "Rechercher"),
1403 ("search_stop", "Arrêter"),
1404 ("searching", "recherche…"),
1405 ("search_n_results", "{} résultats"),
1406 ("search_took", "{} ms"),
1407 ("search_stopped", "arrêté · {} résultats"),
1408 ("search_scanned", "{} fichiers parcourus"),
1409 ("search_skipped", "{} ignorés (plus de 10 Mio.)"),
1410 ("search_n_matches", "{} correspondances"),
1411 ("search_in_n_files", "dans {} fichiers"),
1412 ("search_collapse_all", "Tout replier"),
1413 ("search_expand_all", "Tout déplier"),
1414 ("search_shown_of", "{} sur {} affichés"),
1415 ("search_show_more", "Afficher {} de plus"),
1416 ("search_no_results", "aucun résultat"),
1417 ("saving", "Enregistrement…"),
1418 ("profile", "Profil"),
1419 ("server", "Serveur"),
1420 ("profile_saved", "Profil enregistré"),
1421 (
1422 "profile_save_err",
1423 "Impossible d'enregistrer le profil : {}",
1424 ),
1425 ("settings_saved", "Paramètres enregistrés"),
1426 (
1427 "settings_save_err",
1428 "Impossible d'enregistrer les paramètres : {}",
1429 ),
1430 ("single_click_label", "Ouvrir au clic simple"),
1431 (
1432 "single_click_desc",
1433 "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.",
1434 ),
1435 ("save_profile", "Enregistrer le profil"),
1436 ("allow_rw_shares", "Autoriser les partages modifiables"),
1437 (
1438 "allow_rw_shares_desc",
1439 "Autoriser les utilisateurs à créer des liens de partage en lecture-écriture. Désactivé par défaut.",
1440 ),
1441 ("save_settings", "Enregistrer les paramètres"),
1442 ("language", "Langue"),
1443 (
1444 "language_desc",
1445 "La langue de l'interface. « Automatique » utilise la langue de votre navigateur.",
1446 ),
1447 ("lang_auto", "Automatique"),
1448 ("lang_en", "Anglais"),
1449 ("lang_de", "Allemand"),
1450 ("lang_fr", "Français"),
1451 (
1452 "delete_user_confirm",
1453 "Supprimer l'utilisateur « {} » ? Cette action est irréversible.",
1454 ),
1455 ("user_delete_err", "Impossible de supprimer l'utilisateur"),
1456 ("new_user", "Nouvel utilisateur"),
1457 ("edit", "Modifier"),
1458 ("delete_user", "Supprimer l'utilisateur"),
1459 (
1460 "cant_delete_self",
1461 "Vous ne pouvez pas supprimer votre propre compte",
1462 ),
1463 ("root_dup_err", "Ce dossier est déjà dans la liste."),
1464 ("name_required", "Un nom est requis."),
1465 (
1466 "pw_short",
1467 "Le mot de passe doit contenir au moins 8 caractères.",
1468 ),
1469 ("need_folder", "Donnez au moins un dossier à l'utilisateur."),
1470 ("user_created", "Utilisateur créé"),
1471 ("user_updated", "Utilisateur mis à jour"),
1472 (
1473 "user_save_err",
1474 "Impossible d'enregistrer l'utilisateur : {}",
1475 ),
1476 ("password", "Mot de passe"),
1477 (
1478 "new_password_keep",
1479 "Nouveau mot de passe (laisser vide pour conserver)",
1480 ),
1481 ("administrator", "Administrateur"),
1482 ("active_can_signin", "Actif (peut se connecter)"),
1483 ("remove_folder", "Retirer le dossier"),
1484 ("create_user", "Créer l'utilisateur"),
1485 ("save", "Enregistrer"),
1486 ("edit_user_title", "Modifier « {} »"),
1487 ("folders", "Dossiers"),
1488 (
1489 "folders_paths_hint",
1490 "Les chemins sont relatifs à la racine du serveur. « . » désigne toute la racine.",
1491 ),
1492 ("add", "Ajouter"),
1493 ("mode_rw", "lecture-écriture"),
1494 ("expiry_1h", "1 heure"),
1495 ("expiry_1d", "1 jour"),
1496 ("expiry_1w", "1 semaine"),
1497 ("expiry_1mo", "1 mois"),
1498 ("meta_expires", "expire le {}"),
1499 ("meta_created", "créé le {}"),
1500 ("mode_ro", "lecture seule"),
1501 ("share_not_found", "Partage introuvable"),
1502 (
1503 "share_not_found_msg",
1504 "Ce lien est invalide ou le partage a été supprimé.",
1505 ),
1506 ("share_expired", "Partage expiré"),
1507 ("share_expired_msg", "Ce lien de partage n'est plus actif."),
1508 ("copy_manual", "Copiez le lien manuellement"),
1509 ("link_copied", "Lien copié dans le presse-papiers"),
1510 ("share_create_err", "Impossible de créer le partage : {}"),
1511 ("share_link", "Lien de partage"),
1512 (
1513 "share_perm_rw",
1514 "Toute personne possédant ce lien peut lire et modifier « {} ».",
1515 ),
1516 (
1517 "share_perm_ro",
1518 "Toute personne possédant ce lien peut lire « {} ».",
1519 ),
1520 ("share_title", "Partager {}"),
1521 (
1522 "share_dialog_hint",
1523 "Crée un lien pour cet élément. Les liens modifiables permettent aux destinataires de téléverser et de modifier.",
1524 ),
1525 ("expires", "Expire"),
1526 ("never", "Jamais"),
1527 ("custom", "Personnalisé…"),
1528 ("until", "Jusqu'au"),
1529 ("expires_at", "Expire le {}"),
1530 (
1531 "delete_partial",
1532 "{} des {} liens n'ont pas pu être supprimés",
1533 ),
1534 ("never_expires", "N'expire jamais"),
1535 (
1536 "allow_editing",
1537 "Autoriser la modification (lecture-écriture)",
1538 ),
1539 (
1540 "rw_disabled",
1541 "Les partages modifiables sont désactivés par le serveur.",
1542 ),
1543 (
1544 "rw_ro_folder",
1545 "Ce dossier est en lecture seule, le partage l'est donc aussi.",
1546 ),
1547 ("creating", "Création…"),
1548 ("create_link", "Créer le lien"),
1549 ("your_shares", "Vos partages"),
1550 ("deleted_n_shares", "{} partage(s) supprimé(s)"),
1551 ("shares_delete_err", "Impossible de supprimer les partages"),
1552 ("delete_all", "Tout supprimer"),
1553 ("no_shares", "Aucun partage pour l'instant"),
1554 (
1555 "shares_view_hint",
1556 "Liens que vous avez créés. Cliquez du bouton droit sur un fichier ou un dossier et choisissez « Partager » pour en ajouter un.",
1557 ),
1558 (
1559 "shares_empty_hint",
1560 "Cliquez du bouton droit sur un fichier ou un dossier et choisissez « Partager » pour créer un lien.",
1561 ),
1562 ("copy_link", "Copier le lien"),
1563 ("delete_share", "Supprimer le partage"),
1564 ("share_deleted", "Partage supprimé"),
1565 ("share_delete_err", "Impossible de supprimer le partage"),
1566 ("delete_all_title", "Tout supprimer ?"),
1567 (
1568 "delete_all_msg",
1569 "Supprimer les {} liens de partage ? Toute personne les possédant perdra l'accès. Cette action est irréversible.",
1570 ),
1571 ("login_subtitle", "Connectez-vous pour continuer."),
1572 (
1573 "login_error",
1574 "Veuillez saisir votre nom et votre mot de passe.",
1575 ),
1576 ("sign_in", "Se connecter"),
1577 (
1578 "setup_name_err",
1579 "Veuillez choisir un nom (1 à 64 caractères).",
1580 ),
1581 (
1582 "setup_pw_mismatch",
1583 "Les mots de passe ne correspondent pas.",
1584 ),
1585 ("repeat_password", "Répéter le mot de passe"),
1586 ("create_admin", "Créer le compte administrateur"),
1587 (
1588 "setup_intro",
1589 "Premier démarrage : créez le compte administrateur. Cet utilisateur peut gérer les autres utilisateurs et les paramètres du serveur.",
1590 ),
1591 ("close_esc", "Fermer (Échap)"),
1592 ("no_preview", "Aperçu non disponible"),
1593 (
1594 "no_preview_msg",
1595 "L’application ne peut pas afficher « {} » ici.",
1596 ),
1597 ("dismiss", "Fermer"),
1598 ("dismiss_error", "Fermer l'erreur"),
1599 ("file_changed", "Fichier modifié sur disque"),
1600 (
1601 "modified_after",
1602 "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.",
1603 ),
1604 ("load_latest", "Charger la version récente"),
1605 ("keep_editing", "Continuer à modifier"),
1606 ("discard", "Abandonner"),
1607 ("unsaved_changes", "Modifications non enregistrées"),
1608 (
1609 "discard_question",
1610 "Abandonner vos modifications et fermer l'éditeur ?",
1611 ),
1612 (k::ERR_INTERNAL, "erreur interne"),
1613 (k::ERR_NOT_SIGNED_IN, "non connecté"),
1614 (
1615 k::ERR_SESSION_EXPIRED,
1616 "session expirée, veuillez vous connecter à nouveau",
1617 ),
1618 (k::ERR_INVALID_CREDENTIALS, "nom ou mot de passe incorrect"),
1619 (k::ERR_ALREADY_SET_UP, "le serveur est déjà configuré"),
1620 (k::ERR_SHARE_NOT_FOUND, "partage introuvable"),
1621 (k::ERR_SHARE_EXPIRED, "ce partage a expiré"),
1622 (
1623 k::ERR_SHARE_TOKEN_FORBIDDEN,
1624 "un jeton de partage ne peut pas être utilisé ici ; connectez-vous à la place",
1625 ),
1626 (k::ERR_NAME_LENGTH, "le nom doit contenir 1 à 64 caractères"),
1627 (
1628 k::ERR_PASSWORD_SHORT,
1629 "le mot de passe doit contenir au moins 8 caractères",
1630 ),
1631 (k::ERR_ADMIN_ONLY, "réservé aux administrateurs"),
1632 (k::ERR_USER_EXISTS, "un utilisateur avec ce nom existe déjà"),
1633 (k::ERR_USER_NOT_FOUND, "utilisateur introuvable"),
1634 (
1635 k::ERR_OWN_ADMIN,
1636 "vous ne pouvez pas retirer vos propres droits d'administrateur",
1637 ),
1638 (
1639 k::ERR_OWN_ACCOUNT,
1640 "vous ne pouvez pas désactiver votre propre compte",
1641 ),
1642 (
1643 k::ERR_LAST_ADMIN,
1644 "impossible de supprimer le dernier administrateur actif",
1645 ),
1646 (
1647 k::ERR_OWN_DELETE,
1648 "vous ne pouvez pas supprimer votre propre compte",
1649 ),
1650 (
1651 k::ERR_LAST_ADMIN_DELETE,
1652 "impossible de supprimer le dernier administrateur actif",
1653 ),
1654 (
1655 k::ERR_BAD_FORMAT,
1656 "le format doit être l'un de : zip, tar, tar.gz, tar.zst",
1657 ),
1658 (k::ERR_NOT_A_FILE, "ce n'est pas un fichier"),
1659 (
1660 k::ERR_TOO_LARGE_PREVIEW,
1661 "fichier trop volumineux pour l'aperçu",
1662 ),
1663 (k::ERR_BAD_ACTION, "action=content attendu"),
1664 (
1665 k::ERR_TOO_LARGE_SAVE,
1666 "fichier trop volumineux pour être enregistré",
1667 ),
1668 (k::ERR_BAD_BODY, "corps de requête invalide"),
1669 (k::ERR_INVALID_LANGUAGE, "balise de langue invalide"),
1670 (
1671 k::ERR_BAD_POST,
1672 "POST attend un téléversement multipart, une mutation JSON ou ?action=mkdir/create-file",
1673 ),
1674 (k::ERR_FOLDER_NAME_REQUIRED, "un nom de dossier est requis"),
1675 (k::ERR_FILE_NAME_REQUIRED, "un nom de fichier est requis"),
1676 (k::ERR_NEW_NAME_REQUIRED, "new_name est requis"),
1677 (k::ERR_DST_REQUIRED, "dst_root_id est requis"),
1678 (k::ERR_PATH_REQUIRED, "un chemin dans le dossier est requis"),
1679 (
1680 k::ERR_BAD_MULTIPART,
1681 "multipart/form-data avec boundary attendu",
1682 ),
1683 (k::ERR_BAD_UPLOAD, "données de téléversement invalides"),
1684 (k::ERR_PART_NO_NAME, "partie sans nom"),
1685 (k::ERR_BAD_PART_NAME, "nom de partie invalide"),
1686 (k::ERR_FOLDER_EXISTS, "un dossier avec ce nom existe déjà"),
1687 (k::ERR_SAVE_FAILED, "impossible d'enregistrer le fichier"),
1688 (k::ERR_NO_FILES_UPLOADED, "aucun fichier n'a été téléversé"),
1689 (k::ERR_FILES_EXIST, "certains fichiers existent déjà"),
1690 (
1691 k::ERR_BAD_UPLOAD_PATH,
1692 "chemin de fichier invalide dans le téléversement",
1693 ),
1694 (k::ERR_NO_SUCH_FOLDER, "aucun tel dossier"),
1695 (k::ERR_READ_ONLY_FOLDER, "dossier en lecture seule"),
1696 (
1697 k::ERR_RW_SHARES_DISABLED,
1698 "les partages modifiables sont désactivés",
1699 ),
1700 (
1701 k::ERR_RW_RO_FOLDER,
1702 "ce dossier est en lecture seule pour vous, il ne peut donc pas être partagé en écriture",
1703 ),
1704 (k::ERR_FS_NOT_FOUND, "dossier introuvable"),
1705 (k::ERR_FS_NOT_A_DIR, "ce n'est pas un dossier"),
1706 (k::ERR_FS_FORBIDDEN, "accès refusé"),
1707 (k::ERR_FS_ROOT_MISSING, "le dossier configuré n'existe plus"),
1708 (k::ERR_FS_CONFLICT, "existe déjà"),
1709 (
1710 k::ERR_SEARCH_FORBIDDEN,
1711 "la recherche nécessite une session connectée",
1712 ),
1713 (
1714 k::ERR_SEARCH_BAD_SCOPE,
1715 "le périmètre doit être name, content ou both",
1716 ),
1717 (
1718 k::ERR_SEARCH_BAD_ROOTS,
1719 "les identifiants de dossiers doivent être des nombres",
1720 ),
1721 (k::ERR_ROOT_FORBIDDEN, "dossier inaccessible"),
1722];
1723
1724#[cfg(test)]
1725mod tests {
1726 use super::*;
1727
1728 fn keys<'a>(table: &'a [(&'a str, &'a str)]) -> Vec<&'a str> {
1729 table.iter().map(|(k, _)| *k).collect()
1730 }
1731
1732 #[test]
1733 fn no_duplicate_keys() {
1734 for (name, table) in [("EN", EN), ("DE", DE), ("FR", FR)] {
1735 let mut ks = keys(table);
1736 ks.sort_unstable();
1737 let dups = ks
1738 .windows(2)
1739 .filter(|w| w[0] == w[1])
1740 .map(|w| w[0])
1741 .collect::<Vec<_>>();
1742 assert!(dups.is_empty(), "{name} duplicates: {dups:?}");
1743 }
1744 }
1745
1746 #[test]
1747 fn translations_cover_all_keys() {
1748 // Full parity: DE and FR must translate every EN key (both
1749 // directions), so no string can silently fall back to English.
1750 let en = keys(EN);
1751 for (name, table) in [("DE", DE), ("FR", FR)] {
1752 let other = keys(table);
1753 assert_eq!(
1754 en.len(),
1755 other.len(),
1756 "{name} has {} keys, EN has {}",
1757 other.len(),
1758 en.len()
1759 );
1760 for k in &other {
1761 assert!(en.contains(k), "{name} key not in EN: {k}");
1762 }
1763 }
1764 }
1765
1766 /// The parity test above only compares the tables against each other, so
1767 /// a key missing from *all* of them (e.g. a `k` constant added without
1768 /// its translations) slips through. Scan the `k` module's source for
1769 /// `pub const NAME: &str = "key";` and require every key in `EN`.
1770 #[test]
1771 fn all_k_constants_exist_in_en() {
1772 let src = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/src/i18n.rs"))
1773 .expect("read i18n.rs");
1774 let k_start = src.find("pub mod k {").expect("k module");
1775 // The k module's closing brace is the first column-0 `}` after it.
1776 let k_end = k_start + src[k_start..].find("\n}").expect("k module end");
1777 // Lines look like: `pub const NAME: &str = "key";`
1778 let parse_line = |l: &str| {
1779 let l = l.trim();
1780 let mut parts = l.splitn(3, char::is_whitespace);
1781 let _kw = parts.next()?;
1782 let _const_kw = parts.next()?;
1783 let rest = parts.next()?; // `NAME: &str = "key";`
1784 let (name_ty, value) = rest.split_once('=')?;
1785 let ty = name_ty.rsplit_once(':')?.1.trim();
1786 let val = value.trim().trim_end_matches(';').trim().trim_matches('"');
1787 (ty == "&str").then_some(val.to_string())
1788 };
1789 let keys: Vec<String> = src[k_start..k_end]
1790 .lines()
1791 .filter_map(parse_line)
1792 .filter(|k| !k.is_empty())
1793 .collect();
1794 assert!(keys.len() > 100, "scan found only {} keys", keys.len());
1795 let missing: Vec<&String> = keys
1796 .iter()
1797 .filter(|k| EN.iter().all(|(t, _)| *t != k.as_str()))
1798 .collect();
1799 assert!(
1800 missing.is_empty(),
1801 "k constants without an EN translation: {missing:?}"
1802 );
1803 }
1804}
1805