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