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