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